mirror of
https://github.com/githubhjs/CLIProxyAPIPlus.git
synced 2026-07-14 02:25:26 +00:00
feat(executor): add HttpRequest method with credential injection for GitHub Copilot and Kiro executors
This commit is contained in:
@@ -63,10 +63,38 @@ func NewGitHubCopilotExecutor(cfg *config.Config) *GitHubCopilotExecutor {
|
|||||||
func (e *GitHubCopilotExecutor) Identifier() string { return githubCopilotAuthType }
|
func (e *GitHubCopilotExecutor) Identifier() string { return githubCopilotAuthType }
|
||||||
|
|
||||||
// PrepareRequest implements ProviderExecutor.
|
// PrepareRequest implements ProviderExecutor.
|
||||||
func (e *GitHubCopilotExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error {
|
func (e *GitHubCopilotExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth.Auth) error {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx := req.Context()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
apiToken, errToken := e.ensureAPIToken(ctx, auth)
|
||||||
|
if errToken != nil {
|
||||||
|
return errToken
|
||||||
|
}
|
||||||
|
e.applyHeaders(req, apiToken)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HttpRequest injects GitHub Copilot credentials into the request and executes it.
|
||||||
|
func (e *GitHubCopilotExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth, req *http.Request) (*http.Response, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("github-copilot executor: request is nil")
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = req.Context()
|
||||||
|
}
|
||||||
|
httpReq := req.WithContext(ctx)
|
||||||
|
if errPrepare := e.PrepareRequest(httpReq, auth); errPrepare != nil {
|
||||||
|
return nil, errPrepare
|
||||||
|
}
|
||||||
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
|
return httpClient.Do(httpReq)
|
||||||
|
}
|
||||||
|
|
||||||
// Execute handles non-streaming requests to GitHub Copilot.
|
// Execute handles non-streaming requests to GitHub Copilot.
|
||||||
func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) {
|
func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) {
|
||||||
apiToken, errToken := e.ensureAPIToken(ctx, auth)
|
apiToken, errToken := e.ensureAPIToken(ctx, auth)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
|
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -218,7 +217,48 @@ func NewKiroExecutor(cfg *config.Config) *KiroExecutor {
|
|||||||
func (e *KiroExecutor) Identifier() string { return "kiro" }
|
func (e *KiroExecutor) Identifier() string { return "kiro" }
|
||||||
|
|
||||||
// PrepareRequest prepares the HTTP request before execution.
|
// PrepareRequest prepares the HTTP request before execution.
|
||||||
func (e *KiroExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error { return nil }
|
func (e *KiroExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth.Auth) error {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
accessToken, _ := kiroCredentials(auth)
|
||||||
|
if strings.TrimSpace(accessToken) == "" {
|
||||||
|
return statusErr{code: http.StatusUnauthorized, msg: "missing access token"}
|
||||||
|
}
|
||||||
|
if isIDCAuth(auth) {
|
||||||
|
req.Header.Set("User-Agent", kiroIDEUserAgent)
|
||||||
|
req.Header.Set("X-Amz-User-Agent", kiroIDEAmzUserAgent)
|
||||||
|
req.Header.Set("x-amzn-kiro-agent-mode", kiroIDEAgentModeSpec)
|
||||||
|
} else {
|
||||||
|
req.Header.Set("User-Agent", kiroUserAgent)
|
||||||
|
req.Header.Set("X-Amz-User-Agent", kiroFullUserAgent)
|
||||||
|
}
|
||||||
|
req.Header.Set("Amz-Sdk-Request", "attempt=1; max=3")
|
||||||
|
req.Header.Set("Amz-Sdk-Invocation-Id", uuid.New().String())
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
var attrs map[string]string
|
||||||
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
|
}
|
||||||
|
util.ApplyCustomHeadersFromAttrs(req, attrs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpRequest injects Kiro credentials into the request and executes it.
|
||||||
|
func (e *KiroExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth, req *http.Request) (*http.Response, error) {
|
||||||
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("kiro executor: request is nil")
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = req.Context()
|
||||||
|
}
|
||||||
|
httpReq := req.WithContext(ctx)
|
||||||
|
if errPrepare := e.PrepareRequest(httpReq, auth); errPrepare != nil {
|
||||||
|
return nil, errPrepare
|
||||||
|
}
|
||||||
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
|
return httpClient.Do(httpReq)
|
||||||
|
}
|
||||||
|
|
||||||
// Execute sends the request to Kiro API and returns the response.
|
// Execute sends the request to Kiro API and returns the response.
|
||||||
// Supports automatic token refresh on 401/403 errors.
|
// Supports automatic token refresh on 401/403 errors.
|
||||||
@@ -1004,7 +1044,7 @@ func findRealThinkingEndTag(content string, alreadyInCodeBlock, alreadyInInlineC
|
|||||||
discussionPatterns := []string{
|
discussionPatterns := []string{
|
||||||
"标签", "返回", "输出", "包含", "使用", "解析", "转换", "生成", // Chinese
|
"标签", "返回", "输出", "包含", "使用", "解析", "转换", "生成", // Chinese
|
||||||
"tag", "return", "output", "contain", "use", "parse", "emit", "convert", "generate", // English
|
"tag", "return", "output", "contain", "use", "parse", "emit", "convert", "generate", // English
|
||||||
"<thinking>", // discussing both tags together
|
"<thinking>", // discussing both tags together
|
||||||
"`</thinking>`", // explicitly in inline code
|
"`</thinking>`", // explicitly in inline code
|
||||||
}
|
}
|
||||||
isDiscussion := false
|
isDiscussion := false
|
||||||
@@ -1852,7 +1892,6 @@ func (e *KiroExecutor) extractEventTypeFromBytes(headers []byte) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Response building functions moved to internal/translator/kiro/claude/kiro_claude_response.go
|
// NOTE: Response building functions moved to internal/translator/kiro/claude/kiro_claude_response.go
|
||||||
// The executor now uses kiroclaude.BuildClaudeResponse() and kiroclaude.ExtractThinkingFromContent() instead
|
// The executor now uses kiroclaude.BuildClaudeResponse() and kiroclaude.ExtractThinkingFromContent() instead
|
||||||
|
|
||||||
@@ -1889,18 +1928,18 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
|
|||||||
var lastReportedOutputTokens int64 // Last reported output token count
|
var lastReportedOutputTokens int64 // Last reported output token count
|
||||||
|
|
||||||
// Upstream usage tracking - Kiro API returns credit usage and context percentage
|
// Upstream usage tracking - Kiro API returns credit usage and context percentage
|
||||||
var upstreamCreditUsage float64 // Credit usage from upstream (e.g., 1.458)
|
var upstreamCreditUsage float64 // Credit usage from upstream (e.g., 1.458)
|
||||||
var upstreamContextPercentage float64 // Context usage percentage from upstream (e.g., 78.56)
|
var upstreamContextPercentage float64 // Context usage percentage from upstream (e.g., 78.56)
|
||||||
var hasUpstreamUsage bool // Whether we received usage from upstream
|
var hasUpstreamUsage bool // Whether we received usage from upstream
|
||||||
|
|
||||||
// Translator param for maintaining tool call state across streaming events
|
// Translator param for maintaining tool call state across streaming events
|
||||||
// IMPORTANT: This must persist across all TranslateStream calls
|
// IMPORTANT: This must persist across all TranslateStream calls
|
||||||
var translatorParam any
|
var translatorParam any
|
||||||
|
|
||||||
// Thinking mode state tracking - tag-based parsing for <thinking> tags in content
|
// Thinking mode state tracking - tag-based parsing for <thinking> tags in content
|
||||||
inThinkBlock := false // Whether we're currently inside a <thinking> block
|
inThinkBlock := false // Whether we're currently inside a <thinking> block
|
||||||
isThinkingBlockOpen := false // Track if thinking content block SSE event is open
|
isThinkingBlockOpen := false // Track if thinking content block SSE event is open
|
||||||
thinkingBlockIndex := -1 // Index of the thinking content block
|
thinkingBlockIndex := -1 // Index of the thinking content block
|
||||||
var accumulatedThinkingContent strings.Builder // Accumulate thinking content for token counting
|
var accumulatedThinkingContent strings.Builder // Accumulate thinking content for token counting
|
||||||
|
|
||||||
// Buffer for handling partial tag matches at chunk boundaries
|
// Buffer for handling partial tag matches at chunk boundaries
|
||||||
@@ -2319,16 +2358,16 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
|
|||||||
|
|
||||||
lastUsageUpdateLen = accumulatedContent.Len()
|
lastUsageUpdateLen = accumulatedContent.Len()
|
||||||
lastUsageUpdateTime = time.Now()
|
lastUsageUpdateTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TAG-BASED THINKING PARSING: Parse <thinking> tags from content
|
// TAG-BASED THINKING PARSING: Parse <thinking> tags from content
|
||||||
// Combine pending content with new content for processing
|
// Combine pending content with new content for processing
|
||||||
pendingContent.WriteString(contentDelta)
|
pendingContent.WriteString(contentDelta)
|
||||||
processContent := pendingContent.String()
|
processContent := pendingContent.String()
|
||||||
pendingContent.Reset()
|
pendingContent.Reset()
|
||||||
|
|
||||||
// Process content looking for thinking tags
|
// Process content looking for thinking tags
|
||||||
for len(processContent) > 0 {
|
for len(processContent) > 0 {
|
||||||
if inThinkBlock {
|
if inThinkBlock {
|
||||||
// We're inside a thinking block, look for </thinking>
|
// We're inside a thinking block, look for </thinking>
|
||||||
endIdx := strings.Index(processContent, kirocommon.ThinkingEndTag)
|
endIdx := strings.Index(processContent, kirocommon.ThinkingEndTag)
|
||||||
@@ -2503,7 +2542,7 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
|
|||||||
processContent = ""
|
processContent = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tool uses in response (with deduplication)
|
// Handle tool uses in response (with deduplication)
|
||||||
@@ -2927,7 +2966,7 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
|
|||||||
// Calculate input tokens from context percentage
|
// Calculate input tokens from context percentage
|
||||||
// Using 200k as the base since that's what Kiro reports against
|
// Using 200k as the base since that's what Kiro reports against
|
||||||
calculatedInputTokens := int64(upstreamContextPercentage * 200000 / 100)
|
calculatedInputTokens := int64(upstreamContextPercentage * 200000 / 100)
|
||||||
|
|
||||||
// Only use calculated value if it's significantly different from local estimate
|
// Only use calculated value if it's significantly different from local estimate
|
||||||
// This provides more accurate token counts based on upstream data
|
// This provides more accurate token counts based on upstream data
|
||||||
if calculatedInputTokens > 0 {
|
if calculatedInputTokens > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user