120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
package clients
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type SimulationClient struct{}
|
|
|
|
func (c SimulationClient) Run(ctx context.Context, request Request) (Response, error) {
|
|
_ = ctx
|
|
profile := simulationProfile(request)
|
|
if profile == "retryable_failure" {
|
|
return Response{}, &ClientError{Code: "server_error", Message: "simulated retryable failure", Retryable: true}
|
|
}
|
|
if profile == "fatal_failure" || profile == "non_retryable_failure" {
|
|
return Response{}, &ClientError{Code: "bad_request", Message: "simulated non-retryable failure", Retryable: false}
|
|
}
|
|
return Response{
|
|
Result: simulatedResult(request),
|
|
Usage: simulatedUsage(request),
|
|
Progress: simulatedProgress(request),
|
|
}, nil
|
|
}
|
|
|
|
func simulationProfile(request Request) string {
|
|
if value := stringValue(request.Candidate.Credentials, "simulationFailure"); value != "" {
|
|
return value
|
|
}
|
|
if value := stringValue(request.Candidate.PlatformConfig, "simulationFailure"); value != "" {
|
|
return value
|
|
}
|
|
if value := stringValue(request.Body, "simulationProfile"); value != "" {
|
|
return value
|
|
}
|
|
if value := stringValue(request.Body, "testProfile"); value != "" {
|
|
return value
|
|
}
|
|
return "success"
|
|
}
|
|
|
|
func simulatedResult(request Request) map[string]any {
|
|
switch request.Kind {
|
|
case "chat.completions":
|
|
return map[string]any{
|
|
"id": "chatcmpl-simulated",
|
|
"object": "chat.completion",
|
|
"created": nowUnix(),
|
|
"model": request.Model,
|
|
"choices": []any{map[string]any{
|
|
"index": 0,
|
|
"finish_reason": "stop",
|
|
"message": map[string]any{
|
|
"role": "assistant",
|
|
"content": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
|
|
},
|
|
}},
|
|
"usage": map[string]any{"prompt_tokens": 12, "completion_tokens": 8, "total_tokens": 20},
|
|
}
|
|
case "responses":
|
|
return map[string]any{
|
|
"id": "resp-simulated",
|
|
"object": "response",
|
|
"created_at": nowUnix(),
|
|
"model": request.Model,
|
|
"output_text": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
|
|
"usage": map[string]any{"input_tokens": 12, "output_tokens": 8, "total_tokens": 20},
|
|
}
|
|
case "images.edits":
|
|
return map[string]any{
|
|
"id": "img-edit-simulated",
|
|
"created": nowUnix(),
|
|
"model": request.Model,
|
|
"data": []any{map[string]any{
|
|
"url": "/static/simulation/image-edit.png",
|
|
"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation image edit"),
|
|
}},
|
|
}
|
|
default:
|
|
return map[string]any{
|
|
"id": "img-simulated",
|
|
"created": nowUnix(),
|
|
"model": request.Model,
|
|
"data": []any{map[string]any{
|
|
"url": "/static/simulation/image.png",
|
|
"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation image"),
|
|
}},
|
|
}
|
|
}
|
|
}
|
|
|
|
func simulatedUsage(request Request) Usage {
|
|
if request.ModelType == "chat" || request.Kind == "responses" {
|
|
return Usage{InputTokens: 12, OutputTokens: 8, TotalTokens: 20}
|
|
}
|
|
return Usage{}
|
|
}
|
|
|
|
func simulatedProgress(request Request) []Progress {
|
|
provider := request.Candidate.Provider
|
|
if provider == "" {
|
|
provider = "simulation"
|
|
}
|
|
return []Progress{
|
|
{Phase: "normalizing", Progress: 0.2, Message: "request normalized", Payload: map[string]any{"provider": provider}},
|
|
{Phase: "submitting", Progress: 0.55, Message: "simulation client submitted", Payload: map[string]any{"clientId": request.Candidate.ClientID}},
|
|
{Phase: "fetching_result", Progress: 0.85, Message: "simulation result ready", Payload: map[string]any{"kind": request.Kind}},
|
|
}
|
|
}
|
|
|
|
func firstNonEmptyPrompt(body map[string]any, fallback string) string {
|
|
for _, key := range []string{"prompt", "input"} {
|
|
if value := strings.TrimSpace(stringValue(body, key)); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return fallback
|
|
}
|