Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
|
|
@ -0,0 +1,180 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
|
||||
)
|
||||
|
||||
type rpcStreamEmitRequest struct {
|
||||
StreamID string `json:"stream_id"`
|
||||
Payload []byte `json:"payload,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type rpcStreamCloseRequest struct {
|
||||
StreamID string `json:"stream_id"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func emitPluginStreamChunk(streamID string, payload []byte) error {
|
||||
if strings.TrimSpace(streamID) == "" {
|
||||
return fmt.Errorf("plugin stream id is required")
|
||||
}
|
||||
_, errCall := callHost(pluginabi.MethodHostStreamEmit, rpcStreamEmitRequest{
|
||||
StreamID: streamID,
|
||||
Payload: payload,
|
||||
})
|
||||
return errCall
|
||||
}
|
||||
|
||||
func closePluginStream(streamID, errMsg string) {
|
||||
if strings.TrimSpace(streamID) == "" {
|
||||
return
|
||||
}
|
||||
_, _ = callHost(pluginabi.MethodHostStreamClose, rpcStreamCloseRequest{
|
||||
StreamID: streamID,
|
||||
Error: strings.TrimSpace(errMsg),
|
||||
})
|
||||
}
|
||||
|
||||
func looksLikeOpenAIResponsesSSE(payload []byte) bool {
|
||||
if len(payload) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(payload)
|
||||
if strings.Contains(s, "event: message_start") {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(s, "event: response.") ||
|
||||
strings.Contains(s, `"type":"response.`) ||
|
||||
strings.Contains(s, `"type": "response.`)
|
||||
}
|
||||
|
||||
func runWebSearchStreamOrchestration(ctx context.Context, exec pluginapi.ExecutorRequest, hostCallbackID, pluginStreamID string) error {
|
||||
cfg := loadedConfig()
|
||||
req := pluginapi.ModelRouteRequest{
|
||||
SourceFormat: "claude",
|
||||
RequestedModel: strings.TrimSpace(exec.Model),
|
||||
Body: claudeRequestBody(exec),
|
||||
AvailableProviders: availableProvidersFromMetadata(exec.Metadata),
|
||||
}
|
||||
return runOrderedExecutionPlansStream(ctx, exec, hostCallbackID, pluginStreamID, cfg, buildExecutionPlansForExecute(cfg, req))
|
||||
}
|
||||
|
||||
func runOrderedExecutionPlansStream(ctx context.Context, exec pluginapi.ExecutorRequest, hostCallbackID, pluginStreamID string, cfg pluginConfig, plans []executionPlan) error {
|
||||
if len(plans) == 0 {
|
||||
return fmt.Errorf("web search execution: no backend available")
|
||||
}
|
||||
backends := make([]routeBackend, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
backends = append(backends, p.backend)
|
||||
}
|
||||
ordered := sortBackendsByPenalty(backends)
|
||||
planByBackend := make(map[routeBackend]executionPlan, len(plans))
|
||||
for _, p := range plans {
|
||||
planByBackend[p.backend] = p
|
||||
}
|
||||
|
||||
body := claudeRequestBody(exec)
|
||||
var lastErr error
|
||||
for _, backend := range ordered {
|
||||
plan := planByBackend[backend]
|
||||
switch backend {
|
||||
case backendTavily:
|
||||
payload, _, errRun := runTavilyClaudeStreamWithClient(ctx, exec, newTavilyClient(cfg.TavilyAPIKeys))
|
||||
if errRun != nil {
|
||||
lastErr = errRun
|
||||
continue
|
||||
}
|
||||
if errEmit := emitPluginStreamChunk(pluginStreamID, payload); errEmit != nil {
|
||||
return errEmit
|
||||
}
|
||||
recordBackendSuccess(backend)
|
||||
return nil
|
||||
default:
|
||||
status, errRun := hostModelStreamForwardClaude(ctx, hostCallbackID, plan.model, body, pluginStreamID)
|
||||
if errRun != nil {
|
||||
lastErr = errRun
|
||||
if isRetryableHTTPStatus(hostHTTPStatusFromError(errRun)) {
|
||||
recordBackendFailure(backend)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if isRetryableHTTPStatus(status) {
|
||||
recordBackendFailure(backend)
|
||||
lastErr = fmt.Errorf("host model status %d", status)
|
||||
continue
|
||||
}
|
||||
recordBackendSuccess(backend)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
return fmt.Errorf("web search execution: all backends failed")
|
||||
}
|
||||
|
||||
func hostModelStreamForwardClaude(ctx context.Context, hostCallbackID, execModel string, body []byte, pluginStreamID string) (int, error) {
|
||||
raw, errCall := callHost(pluginabi.MethodHostModelExecuteStream, hostModelExecutionRequest{
|
||||
HostModelExecutionRequest: pluginapi.HostModelExecutionRequest{
|
||||
EntryProtocol: "claude",
|
||||
ExitProtocol: "claude",
|
||||
Model: execModel,
|
||||
Stream: true,
|
||||
Body: body,
|
||||
},
|
||||
HostCallbackID: hostCallbackID,
|
||||
})
|
||||
if errCall != nil {
|
||||
return hostHTTPStatusFromError(errCall), errCall
|
||||
}
|
||||
var resp pluginapi.HostModelStreamResponse
|
||||
if errDecode := json.Unmarshal(raw, &resp); errDecode != nil {
|
||||
return 0, errDecode
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
_ = closeHostModelStream(resp.StreamID)
|
||||
return resp.StatusCode, fmt.Errorf("host model status %d", resp.StatusCode)
|
||||
}
|
||||
if strings.TrimSpace(resp.StreamID) == "" {
|
||||
return 0, fmt.Errorf("host model stream: empty stream_id")
|
||||
}
|
||||
defer func() { _ = closeHostModelStream(resp.StreamID) }()
|
||||
|
||||
firstPayload := true
|
||||
for {
|
||||
chunkRaw, errRead := callHost(pluginabi.MethodHostModelStreamRead, pluginapi.HostModelStreamReadRequest{StreamID: resp.StreamID})
|
||||
if errRead != nil {
|
||||
return hostHTTPStatusFromError(errRead), errRead
|
||||
}
|
||||
var chunk pluginapi.HostModelStreamReadResponse
|
||||
if errDecode := json.Unmarshal(chunkRaw, &chunk); errDecode != nil {
|
||||
return 0, errDecode
|
||||
}
|
||||
if chunk.Error != "" {
|
||||
code := hostHTTPStatusFromError(fmt.Errorf("%s", chunk.Error))
|
||||
return code, fmt.Errorf("%s", chunk.Error)
|
||||
}
|
||||
if len(chunk.Payload) > 0 {
|
||||
if firstPayload && looksLikeOpenAIResponsesSSE(chunk.Payload) {
|
||||
return 0, fmt.Errorf("host model stream returned OpenAI Responses SSE instead of Claude Messages SSE")
|
||||
}
|
||||
firstPayload = false
|
||||
if errEmit := emitPluginStreamChunk(pluginStreamID, bytes.Clone(chunk.Payload)); errEmit != nil {
|
||||
return 0, errEmit
|
||||
}
|
||||
}
|
||||
if chunk.Done {
|
||||
break
|
||||
}
|
||||
}
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
Loading…
Reference in a new issue