| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- package main
- import (
- "encoding/json"
- "flag"
- "fmt"
- "io"
- "log"
- "net/http"
- "os"
- "strings"
- )
- type ModelsResponse struct {
- Object string `json:"object"`
- Data []Model `json:"data"`
- }
- type Model struct {
- ID string `json:"id"`
- Object string `json:"object"`
- Created int64 `json:"created,omitempty"`
- OwnedBy string `json:"owned_by,omitempty"`
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- }
- type Limit struct {
- Context int `json:"context"`
- Output int `json:"output"`
- }
- type OpencodeModel struct {
- Name string `json:"name"`
- Limit Limit `json:"limit"`
- }
- // V2-native shape: "provider" / "npm" / "options"
- type DynamicProviderV2 struct {
- Npm string `json:"npm"`
- Name string `json:"name"`
- Options map[string]string `json:"options"`
- Models map[string]OpencodeModel `json:"models,omitempty"`
- }
- // V1-compatible shape: "providers" / "package" / "settings"
- type DynamicProviderV1 struct {
- Package string `json:"package"`
- Name string `json:"name"`
- Settings map[string]string `json:"settings"`
- Models map[string]OpencodeModel `json:"models,omitempty"`
- }
- // OpencodeConfigV2 is the V2-native top-level shape
- type OpencodeConfigV2 struct {
- Schema string `json:"$schema"`
- Provider map[string]DynamicProviderV2 `json:"provider"`
- }
- // OpencodeConfigV1 is the V1-compatible top-level shape
- type OpencodeConfigV1 struct {
- Schema string `json:"$schema"`
- Providers map[string]DynamicProviderV1 `json:"providers"`
- }
- const (
- formatV2Native = "v2" // default
- formatV1Compat = "v1"
- )
- func getIntFromMetadata(md map[string]interface{}, key string, defaultVal int) int {
- if md == nil {
- return defaultVal
- }
- if v, ok := md[key]; ok {
- if i, ok := v.(float64); ok {
- return int(i)
- }
- }
- return defaultVal
- }
- func buildConfig(providerName, baseURL, token string, format string, data []Model) ([]byte, error) {
- schema := "https://opencode.ai/config.json"
- switch format {
- case formatV2Native:
- return buildV2Config(schema, providerName, baseURL, token, data)
- case formatV1Compat:
- return buildV1Config(schema, providerName, baseURL, token, data)
- default:
- return nil, fmt.Errorf("unsupported format: %s", format)
- }
- }
- func buildV2Config(schema, providerName, baseURL, token string, data []Model) ([]byte, error) {
- provider := DynamicProviderV2{
- Npm: "@ai-sdk/openai-compatible",
- Name: providerName,
- Options: map[string]string{
- "baseURL": baseURL,
- "apiKey": token,
- },
- Models: make(map[string]OpencodeModel, len(data)),
- }
- for _, m := range data {
- ctx := getIntFromMetadata(m.Metadata, "context_window", 131072)
- out := getIntFromMetadata(m.Metadata, "max_output_tokens", 8192)
- provider.Models[m.ID] = OpencodeModel{
- Name: m.ID,
- Limit: Limit{
- Context: ctx,
- Output: out,
- },
- }
- }
- config := OpencodeConfigV2{
- Schema: schema,
- Provider: map[string]DynamicProviderV2{providerName: provider},
- }
- return json.MarshalIndent(config, "", " ")
- }
- func buildV1Config(schema, providerName, baseURL, token string, data []Model) ([]byte, error) {
- provider := DynamicProviderV1{
- Package: "@ai-sdk/openai-compatible",
- Name: providerName,
- Settings: map[string]string{"baseURL": baseURL, "apiKey": token},
- Models: make(map[string]OpencodeModel, len(data)),
- }
- for _, m := range data {
- ctx := getIntFromMetadata(m.Metadata, "context_window", 131072)
- out := getIntFromMetadata(m.Metadata, "max_output_tokens", 8192)
- provider.Models[m.ID] = OpencodeModel{
- Name: m.ID,
- Limit: Limit{
- Context: ctx,
- Output: out,
- },
- }
- }
- config := OpencodeConfigV1{
- Schema: schema,
- Providers: map[string]DynamicProviderV1{providerName: provider},
- }
- return json.MarshalIndent(config, "", " ")
- }
- func main() {
- var providerName, baseURL, apiURL, outputFile, token, format string
- flag.StringVar(&providerName, "provider", "Myprovider", "Name of the provider (default: Myprovider)")
- flag.StringVar(&baseURL, "baseurl", "", "Base URL for the provider (required, e.g., http://localhost:8080/v1)")
- flag.StringVar(&apiURL, "apiurl", "", "API URL to fetch models from (default: <baseurl>/models)")
- flag.StringVar(&token, "token", "", "API token sent as Authorization: Bearer and x-api-key (optional)")
- flag.StringVar(&outputFile, "output", "opencode.jsonc", "Output file name (default: opencode.jsonc)")
- flag.StringVar(&format, "format", formatV2Native, "Output format: v2 (default, V2-native: provider/npm/options) or v1 (V1-compatible: providers/package/settings)")
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
- fmt.Fprintf(os.Stderr, "Examples:\n")
- fmt.Fprintf(os.Stderr, " %s --provider Myprovider --baseurl http://localhost:8080/v1\n", os.Args[0])
- fmt.Fprintf(os.Stderr, " %s --provider OmniRoute --baseurl http://localhost:20128/v1 --format v1\n\n", os.Args[0])
- flag.PrintDefaults()
- }
- flag.Parse()
- if baseURL == "" {
- log.Fatal("--baseurl is required")
- }
- if apiURL == "" {
- apiURL = strings.TrimSuffix(baseURL, "/") + "/models"
- }
- // Validate format
- switch format {
- case formatV2Native, formatV1Compat:
- default:
- log.Fatalf("Invalid --format %q: must be %q or %q", format, formatV2Native, formatV1Compat)
- }
- req, err := http.NewRequest("GET", apiURL, nil)
- if err != nil {
- log.Fatalf("Failed to create request: %v", err)
- }
- if token != "" {
- req.Header.Set("Authorization", "Bearer "+token)
- req.Header.Set("x-api-key", token)
- }
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- log.Fatalf("Failed to fetch %s: %v", apiURL, err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- log.Fatalf("Unexpected status code: %d", resp.StatusCode)
- }
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- log.Fatalf("Failed to read response: %v", err)
- }
- var modelsResp ModelsResponse
- if err := json.Unmarshal(body, &modelsResp); err != nil {
- log.Fatalf("Failed to parse models response: %v", err)
- }
- out, err := buildConfig(providerName, baseURL, token, format, modelsResp.Data)
- if err != nil {
- log.Fatalf("Failed to build config: %v", err)
- }
- if err := os.WriteFile(outputFile, out, 0644); err != nil {
- log.Fatalf("Failed to write %s: %v", outputFile, err)
- }
- fmt.Printf("Successfully wrote %s with %d models for provider %s (format: %s)\n",
- outputFile, len(modelsResp.Data), providerName, format)
- }
|