docs/sdk-advanced.md
This guide explains how to extend the embedded proxy with custom providers and schemas using the SDK. You will:
/v1/modelsThe examples use Go 1.24+ and the v6 module path.
auth.ProviderExecutor that performs outbound calls for a given provider key (e.g., gemini, claude, codex). Executors can also implement RequestPreparer to inject credentials on raw HTTP requests.sdk/translator. The built‑in handlers translate between OpenAI/Gemini/Claude/Codex formats; you can register new ones./v1/models and routing hints.Create a type that satisfies auth.ProviderExecutor.
package myprov
import (
"context"
"net/http"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
clipexec "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
)
type Executor struct{}
func (Executor) Identifier() string { return "myprov" }
// Optional: mutate outbound HTTP requests with credentials
func (Executor) PrepareRequest(req *http.Request, a *coreauth.Auth) error {
// Example: req.Header.Set("Authorization", "Bearer "+a.APIKey)
return nil
}
func (Executor) Execute(ctx context.Context, a *coreauth.Auth, req clipexec.Request, opts clipexec.Options) (clipexec.Response, error) {
// Build HTTP request based on req.Payload (already translated into provider format)
// Use per‑auth transport if provided: transport := a.RoundTripper // via RoundTripperProvider
// Perform call and return provider JSON payload
return clipexec.Response{Payload: []byte(`{"ok":true}`)}, nil
}
func (Executor) ExecuteStream(ctx context.Context, a *coreauth.Auth, req clipexec.Request, opts clipexec.Options) (<-chan clipexec.StreamChunk, error) {
ch := make(chan clipexec.StreamChunk, 1)
go func() { defer close(ch); ch <- clipexec.StreamChunk{Payload: []byte("data: {\"done\":true}\n\n")} }()
return ch, nil
}
func (Executor) Refresh(ctx context.Context, a *coreauth.Auth) (*coreauth.Auth, error) {
// Optionally refresh tokens and return updated auth
return a, nil
}
Register the executor with the core manager before starting the service:
core := coreauth.NewManager(coreauth.NewFileStore(cfg.AuthDir), nil, nil)
core.RegisterExecutor(myprov.Executor{})
svc, _ := cliproxy.NewBuilder().WithConfig(cfg).WithConfigPath(cfgPath).WithCoreAuthManager(core).Build()
If your auth entries use provider "myprov", the manager routes requests to your executor.
The handlers accept OpenAI/Gemini/Claude/Codex inputs. To support a new provider format, register translation functions in sdk/translator’s default registry.
Direction matters:
Example: Convert OpenAI Chat → MyProv Chat and back.
package myprov
import (
"context"
sdktr "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
)
const (
FOpenAI = sdktr.Format("openai.chat")
FMyProv = sdktr.Format("myprov.chat")
)
func init() {
sdktr.Register(FOpenAI, FMyProv,
// Request transform (model, rawJSON, stream)
func(model string, raw []byte, stream bool) []byte { return convertOpenAIToMyProv(model, raw, stream) },
// Response transform (stream & non‑stream)
sdktr.ResponseTransform{
Stream: func(ctx context.Context, model string, originalReq, translatedReq, raw []byte, param *any) []string {
return convertStreamMyProvToOpenAI(model, originalReq, translatedReq, raw)
},
NonStream: func(ctx context.Context, model string, originalReq, translatedReq, raw []byte, param *any) string {
return convertMyProvToOpenAI(model, originalReq, translatedReq, raw)
},
},
)
}
When the OpenAI handler receives a request that should route to myprov, the pipeline uses the registered transforms automatically.
Expose models under /v1/models by registering them in the global model registry using the auth ID (client ID) and provider name.
models := []*cliproxy.ModelInfo{
{ ID: "myprov-pro-1", Object: "model", Type: "myprov", DisplayName: "MyProv Pro 1" },
}
cliproxy.GlobalModelRegistry().RegisterClient(authID, "myprov", models)
The embedded server calls this automatically for built‑in providers; for custom providers, register during startup (e.g., after loading auths) or upon auth registration hooks.
Manager.SetRoundTripperProvider to inject per‑auth *http.Transport (e.g., proxy):
core.SetRoundTripperProvider(myProvider) // returns transport per auth
PrepareRequest and/or call Manager.InjectCredentials(req, authID) to set headers./v0/management/request-log/v0/management/debugconfig.yaml and auths/ are picked up automatically by the watcher