sdk/pluginabi/types.go
Rama main · réplica de lectura del repositorio original.
package pluginabi
import (
"encoding/json"
"strings"
)
const (
// ABIVersion tracks the native C ABI shape (native plugin exports).
ABIVersion uint32 = 1
// SchemaVersion tracks the RPC JSON contract exchanged at plugin.register.
// Version 2 adds request lifecycle completion and active request termination.
// Version 3 omits OriginalRequest/RequestBody on payload stream chunks
// (ChunkIndex >= 0); those fields remain on StreamChunkHeaderInitIndex only.
// Plugins that still need per-chunk request bodies should keep schema_version < 3.
// Version 4 adds upstream WebSocket response event observation.
// Version 5 omits HistoryChunks on payload stream chunks (ChunkIndex >= 0);
// those fields remain on StreamChunkHeaderInitIndex only. Plugins that still need
// per-chunk history chunks should keep schema_version < 5.
// Version 6 preserves raw JSON bodies for plugin management responses.
// Plugins that still require HTML entity escaping on JSON response strings
// should keep schema_version < 6.
// Version 7 lets request interceptors opt out of receiving the request body
// per phase via request_interceptor_omit_body_before/after capabilities and
// lets frontend auth providers opt out via frontend_auth_provider_omit_body /
// frontend_auth_omit_body; the host then omits the request body instead of
// reading, cloning, and Base64-encoding the full payload. Plugins that declare
// the flags on schema_version < 7 (or do not declare them) still receive it.
// Version 8 lets request interceptors opt into raw request body delivery and
// localized byte-range patching per phase via request_interceptor_patch_body_before/after
// (or request_body_patcher_before/after) capabilities and the
// request.patch_body_before / request.patch_body_after methods; the host passes
// raw request body bytes directly across the C ABI without JSON/Base64 wrapping
// and splices validated byte-range edits in a single pass.
// Version 9 lets ModelRegistrar.RegisterModels and ModelProvider.StaticModels
// publish model catalogs for multiple providers in one response via Registrations.
// Version 10 lets a plugin expose multiple OAuth/auth provider identifiers and
// per-entry display metadata via auth.identifier (identifiers/entries) or
// Capabilities.AuthProviders / MultiAuthProvider / AuthProviderEntries.
SchemaVersion uint32 = 10
// SchemaVersionStreamChunkOmitRequestBody is the first schema version that omits
// request bodies on payload stream-chunk interceptor calls.
SchemaVersionStreamChunkOmitRequestBody uint32 = 3
// SchemaVersionWebSocketResponseObserver is the first schema version that supports
// upstream WebSocket response event observation.
SchemaVersionWebSocketResponseObserver uint32 = 4
// SchemaVersionStreamChunkOmitHistory is the first schema version that omits
// history chunks on payload stream-chunk interceptor calls.
SchemaVersionStreamChunkOmitHistory uint32 = 5
// SchemaVersionRawManagementResponse is the first schema version where plugin
// management JSON responses are preserved without HTML-escaping strings.
SchemaVersionRawManagementResponse uint32 = 6
// SchemaVersionRequestInterceptorOmitBody is the first schema version where
// request interceptors may opt out of receiving the request body per phase.
SchemaVersionRequestInterceptorOmitBody uint32 = 7
// SchemaVersionFrontendAuthOmitBody is the first schema version where
// frontend auth providers may opt out of receiving the request body.
SchemaVersionFrontendAuthOmitBody uint32 = 7
// SchemaVersionRequestInterceptorBodyPatch is the first schema version where
// request interceptors may opt into raw request body delivery and byte-range patching.
SchemaVersionRequestInterceptorBodyPatch uint32 = 8
// SchemaVersionMultiProviderModels is the first schema version where model
// registration responses may publish models for multiple providers via Registrations.
SchemaVersionMultiProviderModels uint32 = 9
// SchemaVersionMultiAuthProviders is the first schema version where a plugin
// may register multiple auth provider identifiers.
SchemaVersionMultiAuthProviders uint32 = 10
)
const (
MethodPluginRegister = "plugin.register"
MethodPluginQuiesce = "plugin.quiesce"
MethodPluginReconfigure = "plugin.reconfigure"
MethodPluginShutdown = "plugin.shutdown"
MethodModelRegister = "model.register"
MethodModelStatic = "model.static"
MethodModelForAuth = "model.for_auth"
MethodAuthIdentifier = "auth.identifier"
MethodAuthParse = "auth.parse"
MethodAuthLoginStart = "auth.login.start"
MethodAuthLoginPoll = "auth.login.poll"
MethodAuthRefresh = "auth.refresh"
MethodFrontendAuthIdentifier = "frontend_auth.identifier"
MethodFrontendAuthAuthenticate = "frontend_auth.authenticate"
// MethodSchedulerPick asks a scheduler plugin to select an auth candidate.
MethodSchedulerPick = "scheduler.pick"
// MethodModelRoute asks a router plugin to select a plugin executor for a matching request.
MethodModelRoute = "model.route"
MethodExecutorIdentifier = "executor.identifier"
MethodExecutorExecute = "executor.execute"
MethodExecutorExecuteStream = "executor.execute_stream"
MethodExecutorCountTokens = "executor.count_tokens"
MethodExecutorHTTPRequest = "executor.http_request"
MethodRequestTranslate = "request.translate"
MethodRequestNormalize = "request.normalize"
MethodRequestInterceptBefore = "request.intercept_before"
MethodRequestInterceptAfter = "request.intercept_after"
MethodRequestPatchBodyBefore = "request.patch_body_before"
MethodRequestPatchBodyAfter = "request.patch_body_after"
MethodRequestComplete = "request.complete"
MethodResponseTranslate = "response.translate"
MethodResponseNormalizeBefore = "response.normalize_before"
MethodResponseNormalizeAfter = "response.normalize_after"
MethodResponseInterceptAfter = "response.intercept_after"
MethodResponseInterceptStreamChunk = "response.intercept_stream_chunk"
MethodWebSocketResponseEvent = "websocket.response_event"
MethodThinkingIdentifier = "thinking.identifier"
MethodThinkingApply = "thinking.apply"
MethodUsageHandle = "usage.handle"
MethodCommandLineRegister = "command_line.register"
MethodCommandLineExecute = "command_line.execute"
MethodManagementRegister = "management.register"
MethodManagementHandle = "management.handle"
MethodQuotaIdentifier = "quota.identifier"
MethodQuotaDescribe = "quota.describe"
MethodQuotaFetch = "quota.fetch"
MethodQuotaReset = "quota.reset"
MethodHostHTTPDo = "host.http.do"
MethodHostHTTPDoStream = "host.http.do_stream"
MethodHostHTTPOperationOpen = "host.http.operation_open"
MethodHostHTTPCancel = "host.http.cancel"
MethodHostHTTPStreamRead = "host.http.stream_read"
MethodHostHTTPStreamClose = "host.http.stream_close"
MethodHostModelExecute = "host.model.execute"
MethodHostModelExecuteStream = "host.model.execute_stream"
MethodHostModelStreamRead = "host.model.stream_read"
MethodHostModelStreamClose = "host.model.stream_close"
MethodHostStreamEmit = "host.stream.emit"
MethodHostStreamClose = "host.stream.close"
MethodHostLog = "host.log"
MethodHostAuthList = "host.auth.list"
MethodHostAuthGet = "host.auth.get"
MethodHostAuthGetRuntime = "host.auth.get_runtime"
MethodHostAuthSave = "host.auth.save"
MethodHostAuthModels = "host.auth.models"
MethodHostAffinityLookup = "host.affinity.lookup"
MethodHostRoutingResetCooldown = "host.routing.reset_cooldown"
)
// ErrorCode constants define the machine-readable Error.Code values recognized
// by CPA's routing and cooldown pipeline.
const (
// ErrorCodeCredentialQuota marks a 429 refusal as exhausting or limiting the
// entire credential across all of its models (sets Result.CredentialScope and
// records quota reason "credential_quota"). Plugins may also set
// Error.CredentialScoped = true while keeping a provider-specific Code.
ErrorCodeCredentialQuota = "credential_quota"
// ErrorCodeTransientRateLimit marks a 429 refusal as a transient per-model
// capacity/admission limit that cools only the reporting model until
// RetryAfterSeconds without recording quota exhaustion or advancing backoff.
ErrorCodeTransientRateLimit = "transient_rate_limit"
// ErrorCodeRequestScoped marks a failure as caused by the request itself:
// CPA skips credential/model cooldown and stops credential rotation.
ErrorCodeRequestScoped = "request_scoped"
// ErrorCodeForceCooldown forces cooldown scheduling even when cooling is
// disabled for the credential, falling back to the default transient cooldown
// when no status-specific deadline applies.
ErrorCodeForceCooldown = "force_cooldown"
// ErrorCodeStreamTruncated marks an accepted upstream stream that terminated
// without a completion frame: CPA skips credential cooldown and does not
// replay the request on another credential.
ErrorCodeStreamTruncated = "stream_truncated"
// ErrorCodeConnectionLifecycle marks a client disconnect or session-close
// failure that must skip credential cooldown.
ErrorCodeConnectionLifecycle = "connection_lifecycle"
// ErrorCodeTransientTransport marks a pre-HTTP dial/TLS/DNS/reset failure
// that skips credential cooldown and remains eligible for request retries.
ErrorCodeTransientTransport = "transient_transport"
// ErrorCodeModelNotFound marks the requested model as unavailable on the
// selected credential so CPA cools only that model for 12h (or RetryAfterSeconds).
ErrorCodeModelNotFound = "model_not_found"
// ErrorCodeNotImplemented marks an optional capability (such as count_tokens)
// as unsupported so CPA keeps the credential and model available.
ErrorCodeNotImplemented = "not_implemented"
)
type Envelope struct {
OK bool `json:"ok"`
Result json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
// Error carries a structured plugin failure across RPC envelopes and stream
// error_detail callbacks (host.stream.emit and host.stream.close).
//
// Routing and cooldown behavior is controlled by HTTPStatus, RetryAfterSeconds,
// CredentialScoped, and the recognized ErrorCode* values in Code:
// - CredentialScoped: true (or Code: ErrorCodeCredentialQuota) on a 429 marks
// the entire credential as rate-limited across all of its models
// (reason "credential_quota") until RetryAfterSeconds or the quota backoff.
// - Code: ErrorCodeTransientRateLimit on a 429 cools only the reporting model
// until RetryAfterSeconds without marking quota exceeded.
// - Code: ErrorCodeRequestScoped, ErrorCodeStreamTruncated,
// ErrorCodeConnectionLifecycle, or ErrorCodeTransientTransport skips
// credential/model cooldown.
// - Code: ErrorCodeForceCooldown enforces cooldown even when cooling is disabled.
// - Code: ErrorCodeModelNotFound cools only the requested model as unsupported.
// - Code: ErrorCodeNotImplemented keeps count_tokens failures availability-neutral.
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
Retryable bool `json:"retryable,omitempty"`
// HTTPStatus is the HTTP status code (e.g. 401, 403, 429) to surface to the client.
// When omitted or 0, CPA defaults to HTTP 500 (internal_server_error).
HTTPStatus int `json:"http_status,omitempty"`
// RetryAfterSeconds carries upstream retry guidance for cooldown and response headers.
// It is an optional hint. Schema-version agreement does not negotiate support for
// this field, and older hosts silently ignore it, so plugins must not depend on it
// for correctness. The same pair is accepted as error_detail on host.stream.emit
// and host.stream.close.
RetryAfterSeconds int `json:"retry_after_seconds,omitempty"`
// CredentialScoped reports whether the failure applies to the whole credential
// across all models (for example, an account-wide 429 quota or spend limit)
// rather than only the requested model.
CredentialScoped bool `json:"credential_scoped,omitempty"`
}
// Error implements the error interface for Error.
func (e *Error) Error() string {
if e == nil {
return ""
}
return e.Message
}
// ErrorCode returns the machine-readable error code embedded in the Error.
func (e *Error) ErrorCode() string {
if e == nil {
return ""
}
return e.Code
}
// StatusCode returns the HTTP status code embedded in the Error, or 0 if unset.
func (e *Error) StatusCode() int {
if e == nil {
return 0
}
return e.HTTPStatus
}
// IsCredentialScoped reports whether the Error applies to the entire credential.
func (e *Error) IsCredentialScoped() bool {
if e == nil {
return false
}
return e.CredentialScoped || strings.EqualFold(strings.TrimSpace(e.Code), ErrorCodeCredentialQuota)
}
// IsRequestScoped reports whether the Error is tied to the request rather than the credential.
func (e *Error) IsRequestScoped() bool {
if e == nil || e.IsCredentialScoped() {
return false
}
return strings.EqualFold(strings.TrimSpace(e.Code), ErrorCodeRequestScoped)
}
// IsTransientRateLimit reports whether the Error is a transient per-model capacity 429.
func (e *Error) IsTransientRateLimit() bool {
if e == nil || e.IsCredentialScoped() {
return false
}
return strings.EqualFold(strings.TrimSpace(e.Code), ErrorCodeTransientRateLimit)
}
// IsStreamTruncated reports whether the Error represents a truncated accepted upstream stream.
func (e *Error) IsStreamTruncated() bool {
if e == nil || e.IsCredentialScoped() {
return false
}
return strings.EqualFold(strings.TrimSpace(e.Code), ErrorCodeStreamTruncated)
}
// NewError creates an Error instance with an optional HTTP status code.
func NewError(code, message string, httpStatus ...int) *Error {
status := 0
if len(httpStatus) > 0 {
status = httpStatus[0]
}
return &Error{
Code: code,
Message: message,
HTTPStatus: status,
}
}
// NewErrorEnvelope serializes a failed RPC Envelope containing an Error with an optional HTTP status code.
func NewErrorEnvelope(code, message string, httpStatus ...int) ([]byte, error) {
return json.Marshal(Envelope{
OK: false,
Error: NewError(code, message, httpStatus...),
})
}