浏览代码

Add --format v1/v2: output both V2-native and V1-compatible opencode.jsonc

- --format v2 (default): V2-native shape (provider/npm/options)
- --format v1: V1-compatible shape (providers/package/settings)
- Both are accepted by OpenCode V2
Paul Klumpp 2 周之前
父节点
当前提交
60f6425e36
共有 2 个文件被更改,包括 145 次插入 和 50 次删除
  1. 28 8
      README.md
  2. 117 42
      fetch_models_for_opencode.go

+ 28 - 8
README.md

@@ -22,19 +22,39 @@ config in one go.
 
 ## Usage
 
+```bash
+go run . --provider OmniRoute --baseurl http://localhost:20128/v1 --token sk-1234
 ```
-go run . --provider Myprovider --baseurl http://localhost:8080/v1 --token sk-1234
+
+Output: an `opencode.jsonc` file ready to use — either V2-native (default) or V1-compatible.
+
+### Formats
+
+| Flag              | Top-level key | Provider key      | Config key    |
+|-------------------|---------------|-------------------|---------------|
+| `--format v2` _(default)_ | `"provider"`  | `"npm"`           | `"options"`   |
+| `--format v1`     | `"providers"` | `"package"`       | `"settings"`  |
+
+Both shapes are accepted by OpenCode V2. Use **v2** for new configs, **v1** when you need compatibility with existing tooling or the V1-compatible global config.
+
+```bash
+# V2-native (default)
+go run . --provider OmniRoute --baseurl http://localhost:20128/v1
+
+# V1-compatible
+go run . --provider OmniRoute --baseurl http://localhost:20128/v1 --format v1
 ```
 
 ## Flags
 
-| Flag        | Default            | Description                                 |
-|-------------|--------------------|---------------------------------------------|
-| `-provider` | `Myprovider`       | Provider name                               |
-| `-baseurl`  | – (required)       | Base URL, e.g. `http://localhost:8080/v1`   |
-| `-output`   | `opencode.jsonc`   | Output file                                 |
-| `-token`    | –                  | API token sent as `Authorization: Bearer` and `x-api-key` |
-| `-apiurl`   | `<baseurl>/models` | Alternative URL for fetching the model list |
+| Flag        | Default                        | Description                                 |
+|-------------|--------------------------------|---------------------------------------------|
+| `-provider` | `Myprovider`                   | Provider name                               |
+| `-baseurl`  | – (required)                   | Base URL, e.g. `http://localhost:8080/v1`   |
+| `-output`   | `opencode.jsonc`               | Output file                                 |
+| `-token`    | –                              | API token sent as `Authorization: Bearer` and `x-api-key` |
+| `-apiurl`   | `<baseurl>/models`             | Alternative URL for fetching the model list |
+| `-format`   | `v2`                           | Output format: `v2` (default) or `v1`       |
 
 ## Build
 

+ 117 - 42
fetch_models_for_opencode.go

@@ -34,18 +34,39 @@ type OpencodeModel struct {
 	Limit Limit  `json:"limit"`
 }
 
-type DynamicProvider struct {
-	Npm     string                   `json:"npm"`
-	Name    string                   `json:"name"`
-	Options map[string]string        `json:"options"`
-	Models  map[string]OpencodeModel `json:"models"`
+// 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"`
 }
 
-type OpencodeConfig struct {
-	Schema   string                     `json:"$schema"`
-	Provider map[string]DynamicProvider `json:"provider"`
+// 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
@@ -58,19 +79,93 @@ func getIntFromMetadata(md map[string]interface{}, key string, defaultVal 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 string
+	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, "Example:\n")
-		fmt.Fprintf(os.Stderr, "  %s --provider Myprovider --baseurl http://localhost:8080/v1\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()
 	}
 
@@ -84,6 +179,13 @@ func main() {
 		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)
@@ -112,42 +214,15 @@ func main() {
 		log.Fatalf("Failed to parse models response: %v", err)
 	}
 
-	config := OpencodeConfig{
-		Schema: "https://opencode.ai/config.json",
-		Provider: map[string]DynamicProvider{
-			providerName: {
-				Npm:  "@ai-sdk/openai-compatible",
-				Name: providerName,
-				Options: map[string]string{
-					"baseURL": baseURL,
-					"apiKey":  token,
-				},
-				Models: make(map[string]OpencodeModel),
-			},
-		},
-	}
-
-	for _, m := range modelsResp.Data {
-		ctx := getIntFromMetadata(m.Metadata, "context_window", 131072)
-		out := getIntFromMetadata(m.Metadata, "max_output_tokens", 8192)
-
-		config.Provider[providerName].Models[m.ID] = OpencodeModel{
-			Name: m.ID,
-			Limit: Limit{
-				Context: ctx,
-				Output:  out,
-			},
-		}
-	}
-
-	out, err := json.MarshalIndent(config, "", "  ")
+	out, err := buildConfig(providerName, baseURL, token, format, modelsResp.Data)
 	if err != nil {
-		log.Fatalf("Failed to marshal config: %v", err)
+		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\n", outputFile, len(config.Provider[providerName].Models), providerName)
+	fmt.Printf("Successfully wrote %s with %d models for provider %s (format: %s)\n",
+		outputFile, len(modelsResp.Data), providerName, format)
 }