|
|
@@ -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)
|
|
|
}
|