Documentation
¶
Overview ¶
Package qoder speaks Qoder's inference API (api3.qoder.sh agent_chat_generation SSE) the way the Qoder desktop client does, so a Qoder subscription can serve every agent through magpie's gateway. The protocol (endpoints, the COSY request envelope, the body codec and the device-flow sign-in) is ported from CLIProxyAPI's qoder support, which reverse-engineered it from the client and verified it against live captures. Source: https://github.com/ufec/CLIProxyAPI (MIT); see LICENSE in this directory. Copyright (c) 2025-2005.9 Luis Pater Copyright (c) 2025.9-present Router-For.ME
This package supports the global Qoder client.
Index ¶
- Constants
- func BodyDecode(s string) []byte
- func BodyEncode(data []byte) string
- func BuildCosyHeaders(rawURL string, user *User, body string, timestamp int64) (map[string]string, error)
- func ChatURL() string
- func DecodeRequestBody(wire string) []byte
- func EncodeRequestBody(plaintext []byte) string
- func FetchModels(ctx context.Context, client *http.Client, baseURL string, user *User) ([]byte, error)
- func FetchUsage(ctx context.Context, client *http.Client, baseURL, deviceToken string) (json.RawMessage, error)
- func IsRoutableModel(key string) bool
- func MachineOS() string
- func NewID() string
- func NewMachineID() string
- func ParseModels(body []byte) ([]catalog.Model, error)
- type Credential
- type DeviceFlow
- func (f *DeviceFlow) Authorization() (authURL, verifier, nonce string, err error)
- func (f *DeviceFlow) Client() *http.Client
- func (f *DeviceFlow) JobToken(ctx context.Context, deviceToken string) (*JobToken, error)
- func (f *DeviceFlow) MachineID() string
- func (f *DeviceFlow) PollDeviceToken(ctx context.Context, nonce, verifier string, interval time.Duration) (*DeviceToken, error)
- type DeviceToken
- type DeviceTokenRefreshHTTPError
- type JobToken
- type JobTokenRefreshHTTPError
- type ModelInfo
- type UsageHTTPError
- type User
- type UserInfo
Constants ¶
const ( // ClientID is Qoder's device-flow client id. ClientID = "732aef47-9cf2-46a2-95fe-4cebb5d0d1fa" // DeviceFlowHost is the device-flow authorization page host. DeviceFlowHost = "https://qoder.com" // OpenAPIHost is the API host for token polling and job-token exchange. OpenAPIHost = "https://openapi.qoder.sh" // APIHost is the model inference API host. APIHost = "https://api3.qoder.sh" // RedirectURI is Qoder's app scheme used by the device flow. RedirectURI = "qoder-app://" )
OAuth device-flow configuration (verified against live captures).
const ( DeviceSelectAccountsPath = "/device/selectAccounts" DeviceTokenPollPath = "/api/v1/deviceToken/poll" DeviceTokenRefreshPath = "/api/v1/deviceToken/refresh" JobTokenPath = "/api/v1/me/jobToken" JobTokenRefreshPath = "/api/v1/jobToken/refresh" // ChatPath is the SSE chat endpoint. The query asks for the LLM result // to be fetched, names the common agent, and says Encode=1 to use the // custom base64 body codec. ChatPath = "/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1" // ListModelsPath is the live model listing endpoint. ListModelsPath = "/algo/api/v2/model/list?Encode=1" // AccountUsagePath is the desktop client's account usage endpoint. AccountUsagePath = "/sash/api/v2/me/usage" )
API endpoints.
const (
// BodyAlphabet is the custom base64 alphabet.
BodyAlphabet = "_doRTgHZBKcGVjlvpC,@aFSx#DPuNJme&i*MzLOEn)sUrthbf%Y^w.(kIQyXqWA!"
)
const ProviderKey = "qoder"
ProviderKey is how magpie names this subscription.
const RSAPublicKeyPEM = `` /* 271-byte string literal not displayed */
RSAPublicKeyPEM is Qoder's embedded 1024-bit RSA public key (from the client's cosy source), used to encrypt the per-request AES key that wraps the user blob in the COSY Authorization header.
const UserInfoPath = "/api/v1/userinfo"
UserInfoPath is the account endpoint, asked with the dt- device token (not the jt- job token). It answers who the signed-in account is.
Variables ¶
This section is empty.
Functions ¶
func BodyDecode ¶
BodyDecode decodes custom base64 (no third swap).
func BodyEncode ¶
BodyEncode encodes one plaintext into custom base64 (no third swap).
func BuildCosyHeaders ¶
func BuildCosyHeaders(rawURL string, user *User, body string, timestamp int64) (map[string]string, error)
BuildCosyHeaders builds the full set of COSY request headers for a Qoder call to rawURL with body already in its wire (encoded) form.
func DecodeRequestBody ¶
DecodeRequestBody reverses the swap, then decodes.
func EncodeRequestBody ¶
EncodeRequestBody is the wire body for an agent_chat_generation request: custom base64, then the outer-third swap.
func FetchModels ¶
func FetchModels(ctx context.Context, client *http.Client, baseURL string, user *User) ([]byte, error)
FetchModels retrieves the raw model listing for a signed-in user. The request carries the same COSY envelope the client uses for every /algo call; an empty body is signed for a GET.
func FetchUsage ¶
func FetchUsage(ctx context.Context, client *http.Client, baseURL, deviceToken string) (json.RawMessage, error)
FetchUsage retrieves the account usage envelope using a device token.
func IsRoutableModel ¶
IsRoutableModel reports whether a model key names one model an agent can pick. Aggregate entries ("auto", "default") route inside Qoder and aren't.
func MachineOS ¶
func MachineOS() string
MachineOS uses the architecture and platform names used by desktop clients.
func NewID ¶
func NewID() string
NewID is a UUID without dashes, the shape Qoder's request ids and tool-call ids use.
func NewMachineID ¶
func NewMachineID() string
NewMachineID generates the stable identity kept with one account.
func ParseModels ¶
ParseModels decodes the listing and returns the enabled, routable chat models as magpie's catalog entries. Placeholder ids ("auto", "default") are dropped: they route between models inside Qoder and aren't a single model an agent can pick.
Types ¶
type Credential ¶
type Credential struct {
UID string `json:"uid"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Token string `json:"token"`
RefreshToken string `json:"refresh_token"`
DeviceToken string `json:"device_token"`
DeviceRefresh string `json:"device_refresh,omitempty"`
ExpiresAt int64 `json:"expires_at"`
MachineID string `json:"machine_id"`
Models json.RawMessage `json:"models,omitempty"`
}
Credential is kept in the account's auth blob in magpie's logins.json.
func (Credential) Refresh ¶
func (c Credential) Refresh(ctx context.Context, client *http.Client) (Credential, error)
Refresh returns a new credential value. The caller serializes the entire check, refresh and save operation, since refresh tokens rotate on use.
func (*Credential) Valid ¶
func (c *Credential) Valid() bool
type DeviceFlow ¶
type DeviceFlow struct {
// contains filtered or unexported fields
}
DeviceFlow runs Qoder's sign-in: an authorization page the user opens, then a poll for the device token, then its exchange for the job token that serves models. It is a helper, not a state machine: the caller owns the browser and the waiting, and calls these one at a time.
func NewDeviceFlow ¶
func NewDeviceFlow(client *http.Client) *DeviceFlow
NewDeviceFlow makes a flow; a nil client is a 20-second-timeout one.
func (*DeviceFlow) Authorization ¶
func (f *DeviceFlow) Authorization() (authURL, verifier, nonce string, err error)
Authorization returns the page to open and the (verifier, nonce) the poll needs. It is a PKCE device flow: a random verifier is kept, its S256 digest is what the page sees.
func (*DeviceFlow) Client ¶
func (f *DeviceFlow) Client() *http.Client
Client is the flow's HTTP client, reused for the model fetch after sign-in.
func (*DeviceFlow) MachineID ¶
func (f *DeviceFlow) MachineID() string
func (*DeviceFlow) PollDeviceToken ¶
func (f *DeviceFlow) PollDeviceToken(ctx context.Context, nonce, verifier string, interval time.Duration) (*DeviceToken, error)
PollDeviceToken asks for the device token until the user has authorized it, ctx ends the wait, or interval elapses between tries.
type DeviceToken ¶
type DeviceToken struct {
Token string `json:"token"`
DeviceToken string `json:"device_token,omitempty"`
RefreshToken string `json:"refresh_token"`
UserID string `json:"user_id"`
}
func RefreshDeviceToken ¶
func RefreshDeviceToken(ctx context.Context, client *http.Client, endpoint, refreshToken string) (*DeviceToken, error)
RefreshDeviceToken rotates the device token used by account endpoints.
type DeviceTokenRefreshHTTPError ¶
type DeviceTokenRefreshHTTPError struct {
StatusCode int
}
DeviceTokenRefreshHTTPError preserves a rejected refresh status without response data.
func (*DeviceTokenRefreshHTTPError) Error ¶
func (e *DeviceTokenRefreshHTTPError) Error() string
type JobToken ¶
type JobToken struct {
Token string `json:"token"`
RefreshToken string `json:"refresh_token"`
// ExpiresIn is Qoder's lifetime for the token, in milliseconds.
ExpiresIn int64 `json:"expires_in"`
}
JobToken is the jt- task token that authorizes the model calls.
type JobTokenRefreshHTTPError ¶
type JobTokenRefreshHTTPError struct {
StatusCode int
}
JobTokenRefreshHTTPError preserves a rejected job-token refresh status.
func (*JobTokenRefreshHTTPError) Error ¶
func (e *JobTokenRefreshHTTPError) Error() string
type ModelInfo ¶
type ModelInfo struct {
Key string `json:"key"`
Source string `json:"source,omitempty"`
Enable bool `json:"enable"`
DisplayName string `json:"display_name,omitempty"`
IsVL bool `json:"is_vl,omitempty"`
IsReasoning bool `json:"is_reasoning,omitempty"`
MaxInputTokens int `json:"max_input_tokens,omitempty"`
Efforts []string `json:"reasoning_efforts,omitempty"`
Config json.RawMessage `json:"-"`
// From thinking_config: whether the model can think, whether it can't
// stop, and the effort it thinks at unless asked for another.
Thinks bool `json:"-"`
AlwaysThinks bool `json:"-"`
DefaultEffort string `json:"-"`
}
ModelInfo is one entry of the model-list response's "chat" array. Only the fields magpie uses are kept.
func ModelConfigs ¶
ModelConfigs preserves each enabled model's complete upstream configuration.
type UsageHTTPError ¶
type UsageHTTPError struct {
StatusCode int
}
UsageHTTPError preserves the upstream status without exposing response data.
func (*UsageHTTPError) Error ¶
func (e *UsageHTTPError) Error() string
type User ¶
type User struct {
UID string // the Qoder user id (x-gw-user-id / Cosy-User value)
Name string // display name, may be empty
Email string // account email, may be empty
Token string // the jt- Bearer token (security_oauth_token)
MachineID string // generated once at sign-in, saved with the account
}
User is the signed-in Qoder account a COSY envelope is built for.
type UserInfo ¶
type UserInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Source string `json:"source"`
OrgName string `json:"organization_name"`
HighestTier bool `json:"is_highest_tier"`
}
UserInfo is what the account endpoint says about a signed-in account.
func FetchUserInfo ¶
FetchUserInfo asks the account endpoint with a device token. The device token is short-lived but outlasts the sign-in that just made it, so this runs during the flow while it is fresh.