Files
CLIProxyAPI/docs/sdk-advanced.md
Luis Pater a4767fdd8e feat(auth, docs): add SDK guides and local password support for management
- Added extensive SDK usage guides for `cliproxy`, `sdk/access`, and watcher integration.
- Introduced `--password` flag for specifying local management access passwords.
- Enhanced management API with local password checks to secure localhost requests.
- Updated documentation to reflect the new password functionality.
2025-09-25 11:32:14 +08:00

5.3 KiB
Raw Permalink Blame History

SDK Advanced: Executors & Translators

This guide explains how to extend the embedded proxy with custom providers and schemas using the SDK. You will:

  • Implement a provider executor that talks to your upstream API
  • Register request/response translators for schema conversion
  • Register models so they appear in /v1/models

The examples use Go 1.24+ and the v6 module path.

Concepts

  • Provider executor: a runtime component implementing 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.
  • Translator registry: schema conversion functions routed by sdk/translator. The builtin handlers translate between OpenAI/Gemini/Claude/Codex formats; you can register new ones.
  • Model registry: publishes the list of available models per client/provider to power /v1/models and routing hints.

1) Implement a Provider Executor

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 perauth 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.

2) Register Translators

The handlers accept OpenAI/Gemini/Claude/Codex inputs. To support a new provider format, register translation functions in sdk/translators default registry.

Direction matters:

  • Request: register from inbound schema to provider schema
  • Response: register from provider schema back to inbound schema

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 & nonstream)
    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.

3) Register Models

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 builtin providers; for custom providers, register during startup (e.g., after loading auths) or upon auth registration hooks.

Credentials & Transports

  • Use Manager.SetRoundTripperProvider to inject perauth *http.Transport (e.g., proxy):
    core.SetRoundTripperProvider(myProvider) // returns transport per auth
    
  • For raw HTTP flows, implement PrepareRequest and/or call Manager.InjectCredentials(req, authID) to set headers.

Testing Tips

  • Enable request logging: Management API GET/PUT /v0/management/request-log
  • Toggle debug logs: Management API GET/PUT /v0/management/debug
  • Hot reload changes in config.yaml and auths/ are picked up automatically by the watcher