package ai import "sort" // CapabilityRow is one deterministic row in a provider capability matrix. type CapabilityRow struct { // Provider is the registered provider name. Provider string `json:"provider"` Capabilities } // Capabilities describes the AI interfaces a provider has registered. // It is intentionally based on package registration rather than external // provider marketing claims, so it reflects what this build can actually use. type Capabilities struct { // Model reports whether ai.New can construct a chat/text model provider. Model bool `json:"model"` // Image reports whether ai.NewImage can construct an image model provider. Image bool `json:"image"` // Video reports whether ai.NewVideo can construct a video model provider. Video bool `json:"video"` // Stream reports whether the provider has registered end-to-end token streaming. // Providers that only satisfy the Model interface with ErrStreamingUnsupported // leave this false until their Stream implementation is usable. Stream bool `json:"stream"` // ToolStream reports whether the provider supports agent Stream requests that // include tool schemas. Providers may support plain token streaming while // leaving this false when their streaming API cannot accept tools. ToolStream bool `json:"tool_stream"` } // ProviderCapabilities reports the capabilities registered for provider. func ProviderCapabilities(provider string) Capabilities { _, hasModel := providers[provider] _, hasImage := imageProviders[provider] _, hasVideo := videoProviders[provider] _, hasStream := streamProviders[provider] _, hasToolStream := toolStreamProviders[provider] return Capabilities{ Model: hasModel, Image: hasImage, Video: hasVideo, Stream: hasStream, ToolStream: hasToolStream, } } // CapabilityMatrix returns a snapshot of all registered AI providers and the // interfaces they support. The returned map is a copy and can be modified by // callers without mutating the registry. Use CapabilityRows when rendering a // deterministic table or report. func CapabilityMatrix() map[string]Capabilities { names := map[string]struct{}{} for name := range providers { names[name] = struct{}{} } for name := range imageProviders { names[name] = struct{}{} } for name := range videoProviders { names[name] = struct{}{} } for name := range streamProviders { names[name] = struct{}{} } for name := range toolStreamProviders { names[name] = struct{}{} } matrix := make(map[string]Capabilities, len(names)) for name := range names { matrix[name] = ProviderCapabilities(name) } return matrix } // CapabilityRows returns a deterministic capability support matrix for every // registered AI provider. It is the ordered form of CapabilityMatrix, intended // for CLIs, docs generators, and conformance reports that need stable output. func CapabilityRows() []CapabilityRow { names := RegisteredProviders("") rows := make([]CapabilityRow, 0, len(names)) for _, name := range names { rows = append(rows, CapabilityRow{ Provider: name, Capabilities: ProviderCapabilities(name), }) } return rows } // RegisterStream records that provider has a usable Stream implementation. // Providers should call this from init alongside Register once Stream returns // chunks instead of ErrStreamingUnsupported. func RegisterStream(provider string) { streamProviders[provider] = struct{}{} } // RegisterToolStream records that provider can accept tool schemas in Stream // requests. This is intentionally separate from RegisterStream because some // providers can stream tokens but cannot expose tools while streaming. func RegisterToolStream(provider string) { toolStreamProviders[provider] = struct{}{} } var streamProviders = make(map[string]struct{}) var toolStreamProviders = make(map[string]struct{}) // RegisteredProviders returns the registered provider names in sorted order. // kind may be "model", "image", "video", "stream", "tool_stream", or empty for the union of all // provider registries. func RegisteredProviders(kind string) []string { names := map[string]struct{}{} add := func(registry any) { switch r := registry.(type) { case map[string]NewFunc: for name := range r { names[name] = struct{}{} } case map[string]NewImageFunc: for name := range r { names[name] = struct{}{} } case map[string]NewVideoFunc: for name := range r { names[name] = struct{}{} } case map[string]struct{}: for name := range r { names[name] = struct{}{} } } } switch kind { case "model": add(providers) case "stream": add(streamProviders) case "tool_stream": add(toolStreamProviders) case "image": add(imageProviders) case "video": add(videoProviders) default: add(providers) add(imageProviders) add(videoProviders) } out := make([]string, 0, len(names)) for name := range names { out = append(out, name) } sort.Strings(out) return out }