Detailed changes
@@ -0,0 +1,59 @@
+package openrouter
+
+import (
+ "maps"
+
+ "github.com/charmbracelet/fantasy/ai"
+ openaisdk "github.com/openai/openai-go/v2"
+ "github.com/openai/openai-go/v2/packages/param"
+)
+
+func prepareLanguageModelCall(model ai.LanguageModel, params *openaisdk.ChatCompletionNewParams, call ai.Call) ([]ai.CallWarning, error) {
+ providerOptions := &ProviderOptions{}
+ if v, ok := call.ProviderOptions[Name]; ok {
+ providerOptions, ok = v.(*ProviderOptions)
+ if !ok {
+ return nil, ai.NewInvalidArgumentError("providerOptions", "openrouter provider options should be *openrouter.ProviderOptions", nil)
+ }
+ }
+
+ extraFields := make(map[string]any)
+
+ if providerOptions.Provider != nil {
+ data, err := structToMapJSON(providerOptions.Provider)
+ if err != nil {
+ return nil, err
+ }
+ extraFields["provider"] = data
+ }
+
+ if providerOptions.Reasoning != nil {
+ data, err := structToMapJSON(providerOptions.Reasoning)
+ if err != nil {
+ return nil, err
+ }
+ extraFields["reasoning"] = data
+ }
+
+ if providerOptions.IncludeUsage != nil {
+ extraFields["usage"] = map[string]any{
+ "include": *providerOptions.IncludeUsage,
+ }
+ }
+ if providerOptions.LogitBias != nil {
+ params.LogitBias = providerOptions.LogitBias
+ }
+ if providerOptions.LogProbs != nil {
+ params.Logprobs = param.NewOpt(*providerOptions.LogProbs)
+ }
+ if providerOptions.User != nil {
+ params.User = param.NewOpt(*providerOptions.User)
+ }
+ if providerOptions.ParallelToolCalls != nil {
+ params.ParallelToolCalls = param.NewOpt(*providerOptions.ParallelToolCalls)
+ }
+
+ maps.Copy(extraFields, providerOptions.ExtraBody)
+ params.SetExtraFields(extraFields)
+ return nil, nil
+}
@@ -1,9 +1,10 @@
package openrouter
import (
+ "encoding/json"
+
"github.com/charmbracelet/fantasy/ai"
"github.com/charmbracelet/fantasy/openai"
- openaisdk "github.com/openai/openai-go/v2"
"github.com/openai/openai-go/v2/option"
)
@@ -17,27 +18,12 @@ const (
type Option = func(*options)
-func prepareCallWithOptions(model ai.LanguageModel, params *openaisdk.ChatCompletionNewParams, call ai.Call) ([]ai.CallWarning, error) {
- providerOptions := &ProviderOptions{}
- if v, ok := call.ProviderOptions[Name]; ok {
- providerOptions, ok = v.(*ProviderOptions)
- if !ok {
- return nil, ai.NewInvalidArgumentError("providerOptions", "openrouter provider options should be *openrouter.ProviderOptions", nil)
- }
- }
- _ = providerOptions
-
- // HANDLE OPENROUTER call modification here
-
- return nil, nil
-}
-
func New(opts ...Option) ai.Provider {
providerOptions := options{
openaiOptions: []openai.Option{
openai.WithBaseURL(DefaultURL),
openai.WithLanguageModelOptions(
- openai.WithPrepareLanguageModelCallFunc(prepareCallWithOptions),
+ openai.WithPrepareLanguageModelCallFunc(prepareLanguageModelCall),
),
},
}
@@ -70,3 +56,16 @@ func WithHTTPClient(client option.HTTPClient) Option {
o.openaiOptions = append(o.openaiOptions, openai.WithHTTPClient(client))
}
}
+
+func structToMapJSON(s any) (map[string]any, error) {
+ var result map[string]any
+ jsonBytes, err := json.Marshal(s)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(jsonBytes, &result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+}
@@ -20,51 +20,51 @@ func (*ProviderMetadata) Options() {}
type ReasoningOptions struct {
// Whether reasoning is enabled
- Enabled *bool `json:"enabled"`
+ Enabled *bool `json:"enabled,omitempty"`
// Whether to exclude reasoning from the response
- Exclude *bool `json:"exclude"`
+ Exclude *bool `json:"exclude,omitempty"`
// Maximum number of tokens to use for reasoning
- MaxTokens *int64 `json:"max_tokens"`
+ MaxTokens *int64 `json:"max_tokens,omitempty"`
// Reasoning effort level: "low" | "medium" | "high"
- Effort *ReasoningEffort `json:"effort"`
+ Effort *ReasoningEffort `json:"effort,omitempty"`
}
type Provider struct {
// List of provider slugs to try in order (e.g. ["anthropic", "openai"])
- Order []string `json:"order"`
+ Order []string `json:"order,omitempty"`
// Whether to allow backup providers when primary is unavailable (default: true)
- AllowFallbacks *bool `json:"allow_fallbacks"`
+ AllowFallbacks *bool `json:"allow_fallbacks,omitempty"`
// Only use providers that support all parameters in your request (default: false)
- RequireParameters *bool `json:"require_parameters"`
+ RequireParameters *bool `json:"require_parameters,omitempty"`
// Control whether to use providers that may store data: "allow" | "deny"
- DataCollection *string `json:"data_collection"`
+ DataCollection *string `json:"data_collection,omitempty"`
// List of provider slugs to allow for this request
- Only []string `json:"only"`
+ Only []string `json:"only,omitempty"`
// List of provider slugs to skip for this request
- Ignore []string `json:"ignore"`
+ Ignore []string `json:"ignore,omitempty"`
// List of quantization levels to filter by (e.g. ["int4", "int8"])
- Quantizations []string `json:"quantizations"`
+ Quantizations []string `json:"quantizations,omitempty"`
// Sort providers by "price" | "throughput" | "latency"
- Sort *string `json:"sort"`
+ Sort *string `json:"sort,omitempty"`
}
type ProviderOptions struct {
- Reasoning *ReasoningOptions `json:"reasoning"`
- ExtraBody map[string]any `json:"extra_body"`
- IncludeUsage *bool `json:"include_usage"`
+ Reasoning *ReasoningOptions `json:"reasoning,omitempty"`
+ ExtraBody map[string]any `json:"extra_body,omitempty"`
+ IncludeUsage *bool `json:"include_usage,omitempty"`
// Modify the likelihood of specified tokens appearing in the completion.
// Accepts a map that maps tokens (specified by their token ID) to an associated bias value from -100 to 100.
// The bias is added to the logits generated by the model prior to sampling.
- LogitBias map[string]int64 `json:"logit_bias"`
+ LogitBias map[string]int64 `json:"logit_bias,omitempty"`
// Return the log probabilities of the tokens. Including logprobs will increase the response size.
// Setting to true will return the log probabilities of the tokens that were generated.
- LogProbs *bool `json:"log_probs"`
+ LogProbs *bool `json:"log_probs,omitempty"`
// Whether to enable parallel function calling during tool use. Default to true.
- ParallelToolCalls *bool `json:"parallel_tool_calls"`
+ ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
// A unique identifier representing your end-user, which can help OpenRouter to monitor and detect abuse.
- User *string `json:"user"`
+ User *string `json:"user,omitempty"`
// Provider routing preferences to control request routing behavior
- Provider *Provider `json:"provider"`
+ Provider *Provider `json:"provider,omitempty"`
// TODO: add the web search plugin config
}