From 1b8fb1da3ed22c2ee9c4c87421765750e174b385 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 26 Aug 2026 14:52:14 +0000 Subject: [PATCH] Add the OpenRouter catalog module --- README.md | 30 ++- catalog.go | 19 ++ catalog/backend.json | 9 + catalog/profiles/aion-labs/aion-2.yml | 8 + .../anthropic/claude-fable-latest.yml | 6 + .../anthropic/claude-haiku-latest.yml | 6 + .../profiles/anthropic/claude-opus-latest.yml | 6 + .../anthropic/claude-sonnet-latest.yml | 6 + catalog/profiles/deepseek/deepseek-3-2.yml | 6 + .../profiles/deepseek/deepseek-4-flash.yml | 6 + catalog/profiles/deepseek/deepseek-4-pro.yml | 6 + .../profiles/google/gemini-2-flash-lite.yml | 8 + catalog/profiles/google/gemini-2-flash.yml | 8 + catalog/profiles/google/gemini-2-pro.yml | 8 + .../profiles/google/gemini-3-flash-lite.yml | 8 + .../profiles/google/gemini-flash-latest.yml | 8 + catalog/profiles/google/gemini-pro-latest.yml | 8 + catalog/profiles/google/gemma-4-31b.yml | 8 + catalog/profiles/minimax/minimax-m2.yml | 8 + catalog/profiles/minimax/minimax-m3.yml | 8 + .../profiles/mistral/mistral-large-2512.yml | 6 + .../profiles/mistral/mistral-medium-3-5.yml | 7 + catalog/profiles/mistral/mistral-small-3.yml | 6 + catalog/profiles/mistral/mistral-small-4.yml | 7 + catalog/profiles/nvidia/nemotron-3-ultra.yml | 6 + catalog/profiles/openai/gpt-5-mini.yml | 6 + catalog/profiles/openai/gpt-5-nano.yml | 6 + catalog_test.go | 201 ++++++++++++++++++ docs/release.md | 15 ++ go.mod | 5 + go.sum | 4 + 31 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 catalog.go create mode 100644 catalog/backend.json create mode 100644 catalog/profiles/aion-labs/aion-2.yml create mode 100644 catalog/profiles/anthropic/claude-fable-latest.yml create mode 100644 catalog/profiles/anthropic/claude-haiku-latest.yml create mode 100644 catalog/profiles/anthropic/claude-opus-latest.yml create mode 100644 catalog/profiles/anthropic/claude-sonnet-latest.yml create mode 100644 catalog/profiles/deepseek/deepseek-3-2.yml create mode 100644 catalog/profiles/deepseek/deepseek-4-flash.yml create mode 100644 catalog/profiles/deepseek/deepseek-4-pro.yml create mode 100644 catalog/profiles/google/gemini-2-flash-lite.yml create mode 100644 catalog/profiles/google/gemini-2-flash.yml create mode 100644 catalog/profiles/google/gemini-2-pro.yml create mode 100644 catalog/profiles/google/gemini-3-flash-lite.yml create mode 100644 catalog/profiles/google/gemini-flash-latest.yml create mode 100644 catalog/profiles/google/gemini-pro-latest.yml create mode 100644 catalog/profiles/google/gemma-4-31b.yml create mode 100644 catalog/profiles/minimax/minimax-m2.yml create mode 100644 catalog/profiles/minimax/minimax-m3.yml create mode 100644 catalog/profiles/mistral/mistral-large-2512.yml create mode 100644 catalog/profiles/mistral/mistral-medium-3-5.yml create mode 100644 catalog/profiles/mistral/mistral-small-3.yml create mode 100644 catalog/profiles/mistral/mistral-small-4.yml create mode 100644 catalog/profiles/nvidia/nemotron-3-ultra.yml create mode 100644 catalog/profiles/openai/gpt-5-mini.yml create mode 100644 catalog/profiles/openai/gpt-5-nano.yml create mode 100644 catalog_test.go create mode 100644 docs/release.md create mode 100644 go.mod create mode 100644 go.sum diff --git a/README.md b/README.md index 50d80cd..d4f8041 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# promptkit-backend-openrouter +# Promptkit OpenRouter Catalog +This repository publishes immutable OpenRouter catalog assets for Promptkit. +It supplies catalog data only; it does not implement provider behavior, load +credentials, or make network requests. + +Promptkit owns catalog parsing, validation, runtime behavior, credential +handling, and consumer documentation. This module owns the OpenRouter backend +manifest and its maintained profile data. Consumers should use versions chosen +by their Promptkit release rather than treating this module as a standalone +runtime integration. + +The profile assets originated in Promptkit. Their copyright holder has +intentionally authorized their distribution in this repository under its +LGPL-3.0 terms; this is a deliberate relicensing decision, not an automatic +consequence of extracting a dependency. + +## Compatibility + +Catalog IDs are stable. New profiles are additive. Corrections are released as +compatible catalog updates when they preserve the profile contract; deprecated +profiles are announced before removal in a compatible-major release. Manifest +schema changes follow semantic-version compatibility rules and retain support +for compatible published catalog versions. + +## Validation and releases + +Run the offline Go tests, race tests, vet, build, formatting, hygiene, and +module-resolution checks before release. The complete source-only release +procedure is in [docs/release.md](docs/release.md). diff --git a/catalog.go b/catalog.go new file mode 100644 index 0000000..1a97807 --- /dev/null +++ b/catalog.go @@ -0,0 +1,19 @@ +// Package openrouter supplies immutable OpenRouter catalog assets for +// Promptkit. It does not implement provider behavior or runtime configuration. +package openrouter + +import ( + "embed" + "io/fs" +) + +// Root is the filesystem root containing this module's catalog assets. +const Root = "catalog" + +//go:embed catalog +var catalogFS embed.FS + +// FS returns the immutable embedded filesystem containing the catalog assets. +func FS() fs.FS { + return catalogFS +} diff --git a/catalog/backend.json b/catalog/backend.json new file mode 100644 index 0000000..837cb7a --- /dev/null +++ b/catalog/backend.json @@ -0,0 +1,9 @@ +{ + "schema_version": 1, + "id": "openrouter", + "endpoint": "https://openrouter.ai/api/v1", + "api_key_env": "OPENROUTER_API_KEY", + "concurrency_limit": 16, + "queue_capacity": 1024, + "extra_params": null +} diff --git a/catalog/profiles/aion-labs/aion-2.yml b/catalog/profiles/aion-labs/aion-2.yml new file mode 100644 index 0000000..a22cd8f --- /dev/null +++ b/catalog/profiles/aion-labs/aion-2.yml @@ -0,0 +1,8 @@ +id: aion-2 +backend: openrouter +model: aion-labs/aion-2.0 +temperature: 0.72 +reasoning_effort: high +top_p: 0.95 +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/anthropic/claude-fable-latest.yml b/catalog/profiles/anthropic/claude-fable-latest.yml new file mode 100644 index 0000000..0affde6 --- /dev/null +++ b/catalog/profiles/anthropic/claude-fable-latest.yml @@ -0,0 +1,6 @@ +id: claude-fable-latest +backend: openrouter +model: "~anthropic/claude-fable-latest" +reasoning_effort: high +timeout_seconds: 600 +service_tier: flex diff --git a/catalog/profiles/anthropic/claude-haiku-latest.yml b/catalog/profiles/anthropic/claude-haiku-latest.yml new file mode 100644 index 0000000..e5ada35 --- /dev/null +++ b/catalog/profiles/anthropic/claude-haiku-latest.yml @@ -0,0 +1,6 @@ +id: claude-haiku-latest +backend: openrouter +model: "~anthropic/claude-haiku-latest" +reasoning_effort: medium +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/anthropic/claude-opus-latest.yml b/catalog/profiles/anthropic/claude-opus-latest.yml new file mode 100644 index 0000000..6d628ac --- /dev/null +++ b/catalog/profiles/anthropic/claude-opus-latest.yml @@ -0,0 +1,6 @@ +id: claude-opus-latest +backend: openrouter +model: "~anthropic/claude-opus-latest" +reasoning_effort: high +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/anthropic/claude-sonnet-latest.yml b/catalog/profiles/anthropic/claude-sonnet-latest.yml new file mode 100644 index 0000000..adce8d6 --- /dev/null +++ b/catalog/profiles/anthropic/claude-sonnet-latest.yml @@ -0,0 +1,6 @@ +id: claude-sonnet-latest +backend: openrouter +model: "~anthropic/claude-sonnet-latest" +reasoning_effort: high +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/deepseek/deepseek-3-2.yml b/catalog/profiles/deepseek/deepseek-3-2.yml new file mode 100644 index 0000000..b857d0a --- /dev/null +++ b/catalog/profiles/deepseek/deepseek-3-2.yml @@ -0,0 +1,6 @@ +id: deepseek-3-2 +backend: openrouter +model: deepseek/deepseek-v3.2 +reasoning_effort: high +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/deepseek/deepseek-4-flash.yml b/catalog/profiles/deepseek/deepseek-4-flash.yml new file mode 100644 index 0000000..bffa903 --- /dev/null +++ b/catalog/profiles/deepseek/deepseek-4-flash.yml @@ -0,0 +1,6 @@ +id: deepseek-4-flash +backend: openrouter +model: deepseek/deepseek-v4-flash +#reasoning_effort: medium +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/deepseek/deepseek-4-pro.yml b/catalog/profiles/deepseek/deepseek-4-pro.yml new file mode 100644 index 0000000..ec79389 --- /dev/null +++ b/catalog/profiles/deepseek/deepseek-4-pro.yml @@ -0,0 +1,6 @@ +id: deepseek-4-pro +backend: openrouter +model: deepseek/deepseek-v4-pro +reasoning_effort: high +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/google/gemini-2-flash-lite.yml b/catalog/profiles/google/gemini-2-flash-lite.yml new file mode 100644 index 0000000..970f29e --- /dev/null +++ b/catalog/profiles/google/gemini-2-flash-lite.yml @@ -0,0 +1,8 @@ +id: gemini-2-flash-lite +backend: openrouter +model: "google/gemini-2.5-flash-lite" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemini-2-flash.yml b/catalog/profiles/google/gemini-2-flash.yml new file mode 100644 index 0000000..01bd8d7 --- /dev/null +++ b/catalog/profiles/google/gemini-2-flash.yml @@ -0,0 +1,8 @@ +id: gemini-2-flash +backend: openrouter +model: "google/gemini-2.5-flash" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemini-2-pro.yml b/catalog/profiles/google/gemini-2-pro.yml new file mode 100644 index 0000000..fed78cd --- /dev/null +++ b/catalog/profiles/google/gemini-2-pro.yml @@ -0,0 +1,8 @@ +id: gemini-2-pro +backend: openrouter +model: "google/gemini-2.5-pro" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemini-3-flash-lite.yml b/catalog/profiles/google/gemini-3-flash-lite.yml new file mode 100644 index 0000000..4ff439b --- /dev/null +++ b/catalog/profiles/google/gemini-3-flash-lite.yml @@ -0,0 +1,8 @@ +id: gemini-3-flash-lite +backend: openrouter +model: "google/gemini-3.1-flash-lite" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemini-flash-latest.yml b/catalog/profiles/google/gemini-flash-latest.yml new file mode 100644 index 0000000..89acd90 --- /dev/null +++ b/catalog/profiles/google/gemini-flash-latest.yml @@ -0,0 +1,8 @@ +id: gemini-flash-latest +backend: openrouter +model: "~google/gemini-flash-latest" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemini-pro-latest.yml b/catalog/profiles/google/gemini-pro-latest.yml new file mode 100644 index 0000000..2ce5303 --- /dev/null +++ b/catalog/profiles/google/gemini-pro-latest.yml @@ -0,0 +1,8 @@ +id: gemini-pro-latest +backend: openrouter +model: "~google/gemini-pro-latest" +#temperature: 0.15 +reasoning_effort: high +#top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/google/gemma-4-31b.yml b/catalog/profiles/google/gemma-4-31b.yml new file mode 100644 index 0000000..39c3529 --- /dev/null +++ b/catalog/profiles/google/gemma-4-31b.yml @@ -0,0 +1,8 @@ +id: gemma-4-31b +backend: openrouter +model: google/gemma-4-31b-it:exacto +temperature: 0.15 +reasoning_effort: high +top_p: 0.98 +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/minimax/minimax-m2.yml b/catalog/profiles/minimax/minimax-m2.yml new file mode 100644 index 0000000..385a181 --- /dev/null +++ b/catalog/profiles/minimax/minimax-m2.yml @@ -0,0 +1,8 @@ +id: minimax-m2 +backend: openrouter +model: minimax/minimax-m2.5 +temperature: 0.5 +reasoning_effort: high +top_p: 0.95 +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/minimax/minimax-m3.yml b/catalog/profiles/minimax/minimax-m3.yml new file mode 100644 index 0000000..c0c1bd0 --- /dev/null +++ b/catalog/profiles/minimax/minimax-m3.yml @@ -0,0 +1,8 @@ +id: minimax-m3 +backend: openrouter +model: minimax/minimax-m3 +#temperature: 0.5 +reasoning_effort: high +#top_p: 0.95 +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/mistral/mistral-large-2512.yml b/catalog/profiles/mistral/mistral-large-2512.yml new file mode 100644 index 0000000..c77c9d8 --- /dev/null +++ b/catalog/profiles/mistral/mistral-large-2512.yml @@ -0,0 +1,6 @@ +id: mistral-large-2512 +backend: openrouter +model: mistralai/mistral-large-2512 +temperature: 0.15 +top_p: 0.98 +timeout_seconds: 180 diff --git a/catalog/profiles/mistral/mistral-medium-3-5.yml b/catalog/profiles/mistral/mistral-medium-3-5.yml new file mode 100644 index 0000000..b5201ca --- /dev/null +++ b/catalog/profiles/mistral/mistral-medium-3-5.yml @@ -0,0 +1,7 @@ +id: mistral-medium-3-5 +backend: openrouter +model: mistralai/mistral-medium-3-5 +temperature: 0.15 +reasoning_effort: high +top_p: 0.98 +timeout_seconds: 180 diff --git a/catalog/profiles/mistral/mistral-small-3.yml b/catalog/profiles/mistral/mistral-small-3.yml new file mode 100644 index 0000000..7e01e55 --- /dev/null +++ b/catalog/profiles/mistral/mistral-small-3.yml @@ -0,0 +1,6 @@ +id: mistral-small-3 +backend: openrouter +model: mistralai/mistral-small-3.2-24b-instruct +temperature: 0.05 +top_p: 1.0 +timeout_seconds: 180 diff --git a/catalog/profiles/mistral/mistral-small-4.yml b/catalog/profiles/mistral/mistral-small-4.yml new file mode 100644 index 0000000..2d9b44c --- /dev/null +++ b/catalog/profiles/mistral/mistral-small-4.yml @@ -0,0 +1,7 @@ +id: mistral-small-4 +backend: openrouter +model: mistralai/mistral-small-2603 +temperature: 0.1 +reasoning_effort: high +top_p: 0.98 +timeout_seconds: 180 diff --git a/catalog/profiles/nvidia/nemotron-3-ultra.yml b/catalog/profiles/nvidia/nemotron-3-ultra.yml new file mode 100644 index 0000000..90f84f3 --- /dev/null +++ b/catalog/profiles/nvidia/nemotron-3-ultra.yml @@ -0,0 +1,6 @@ +id: nemotron-3-ultra +backend: openrouter +model: nvidia/nemotron-3-ultra-550b-a55b +reasoning_effort: high +timeout_seconds: 180 +service_tier: flex diff --git a/catalog/profiles/openai/gpt-5-mini.yml b/catalog/profiles/openai/gpt-5-mini.yml new file mode 100644 index 0000000..c9b4bb2 --- /dev/null +++ b/catalog/profiles/openai/gpt-5-mini.yml @@ -0,0 +1,6 @@ +id: gpt-5-mini +backend: openrouter +model: "openai/gpt-5.4-mini" +reasoning_effort: high +timeout_seconds: 240 +service_tier: flex diff --git a/catalog/profiles/openai/gpt-5-nano.yml b/catalog/profiles/openai/gpt-5-nano.yml new file mode 100644 index 0000000..0688ddc --- /dev/null +++ b/catalog/profiles/openai/gpt-5-nano.yml @@ -0,0 +1,6 @@ +id: gpt-5-nano +backend: openrouter +model: "openai/gpt-5.4-nano" +reasoning_effort: high +timeout_seconds: 240 +service_tier: flex diff --git a/catalog_test.go b/catalog_test.go new file mode 100644 index 0000000..645b1d1 --- /dev/null +++ b/catalog_test.go @@ -0,0 +1,201 @@ +package openrouter + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "strings" + "testing" + + "gopkg.in/yaml.v3" +) + +func TestCatalogLayoutAndContents(t *testing.T) { + fsys := FS() + if fsys == nil { + t.Fatal("FS returned nil") + } + + manifestPath := Root + "/backend.json" + manifestData, err := fs.ReadFile(fsys, manifestPath) + if err != nil { + t.Fatalf("read manifest: %v", err) + } + assertOpenRouterManifest(t, manifestData) + + profilesRoot := Root + "/profiles" + profileCount := 0 + ids := map[string]string{} + err = fs.WalkDir(fsys, Root, func(path string, entry fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if path == Root || path == profilesRoot || entry.IsDir() { + return nil + } + if path == manifestPath { + if !entry.Type().IsRegular() { + return fmt.Errorf("manifest is not a regular file") + } + return nil + } + if !strings.HasPrefix(path, profilesRoot+"/") { + return fmt.Errorf("unexpected catalog entry %q", path) + } + if !entry.Type().IsRegular() || !strings.HasSuffix(path, ".yml") { + return fmt.Errorf("profile asset %q is not a regular .yml file", path) + } + + data, err := fs.ReadFile(fsys, path) + if err != nil { + return err + } + id := assertOpenRouterProfile(t, path, data) + if previous, found := ids[id]; found { + return fmt.Errorf("duplicate profile ID %q in %s and %s", id, previous, path) + } + ids[id] = path + profileCount++ + return nil + }) + if err != nil { + t.Fatalf("validate catalog layout: %v", err) + } + if profileCount == 0 { + t.Fatal("catalog contains no profiles") + } +} + +func assertOpenRouterManifest(t *testing.T, data []byte) { + t.Helper() + + type manifest struct { + SchemaVersion int `json:"schema_version"` + ID string `json:"id"` + Endpoint string `json:"endpoint"` + APIKeyEnv string `json:"api_key_env"` + ConcurrencyLimit int `json:"concurrency_limit"` + QueueCapacity int `json:"queue_capacity"` + ExtraParams json.RawMessage `json:"extra_params"` + } + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + var value manifest + if err := decoder.Decode(&value); err != nil { + t.Fatalf("decode manifest: %v", err) + } + if err := requireJSONEnd(decoder); err != nil { + t.Fatalf("decode manifest: %v", err) + } + if value.SchemaVersion != 1 || + value.ID != "openrouter" || + value.Endpoint != "https://openrouter.ai/api/v1" || + value.APIKeyEnv != "OPENROUTER_API_KEY" || + value.ConcurrencyLimit != 16 || + value.QueueCapacity != 1024 || + string(value.ExtraParams) != "null" { + t.Fatalf("unexpected manifest: %#v", value) + } +} + +func requireJSONEnd(decoder *json.Decoder) error { + var trailing any + if err := decoder.Decode(&trailing); errors.Is(err, io.EOF) { + return nil + } else if err != nil { + return err + } + return errors.New("catalog manifest must contain exactly one JSON value") +} + +func assertOpenRouterProfile(t *testing.T, path string, data []byte) string { + t.Helper() + + var document yaml.Node + decoder := yaml.NewDecoder(bytes.NewReader(data)) + if err := decoder.Decode(&document); err != nil { + t.Fatalf("decode profile %s: %v", path, err) + } + if err := requireYAMLEnd(decoder); err != nil { + t.Fatalf("decode profile %s: %v", path, err) + } + if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode { + t.Fatalf("profile %s must contain one mapping document", path) + } + + fields := yamlFields(document.Content[0]) + idField := fields["id"] + if idField == nil { + t.Fatalf("profile %s has no ID", path) + } + id := strings.TrimSpace(idField.Value) + if id == "" { + t.Fatalf("profile %s has a blank ID", path) + } + if fields["backend"] == nil || fields["backend"].Value != "openrouter" { + t.Fatalf("profile %s does not select openrouter", path) + } + for _, field := range []string{"endpoint", "api_key_env", "api_key"} { + if fields[field] != nil { + t.Fatalf("profile %s contains prohibited %s field", path, field) + } + } + if extraParams := fields["extra_params"]; extraParams != nil { + assertNoSecretKeys(t, path, extraParams) + } + return id +} + +func requireYAMLEnd(decoder *yaml.Decoder) error { + var trailing yaml.Node + if err := decoder.Decode(&trailing); errors.Is(err, io.EOF) { + return nil + } else if err != nil { + return err + } + return errors.New("profile must contain exactly one YAML document") +} + +func yamlFields(mapping *yaml.Node) map[string]*yaml.Node { + fields := make(map[string]*yaml.Node, len(mapping.Content)/2) + for index := 0; index+1 < len(mapping.Content); index += 2 { + fields[mapping.Content[index].Value] = mapping.Content[index+1] + } + return fields +} + +func assertNoSecretKeys(t *testing.T, path string, node *yaml.Node) { + t.Helper() + + forbidden := map[string]bool{ + "api_key": true, + "apikey": true, + "authorization": true, + "credential": true, + "credentials": true, + "password": true, + "secret": true, + "token": true, + "access_token": true, + } + var visit func(*yaml.Node) + visit = func(current *yaml.Node) { + if current.Kind == yaml.MappingNode { + for index := 0; index+1 < len(current.Content); index += 2 { + if forbidden[strings.ToLower(current.Content[index].Value)] { + t.Fatalf("profile %s contains a prohibited extra_params key", path) + } + visit(current.Content[index+1]) + } + return + } + for _, child := range current.Content { + visit(child) + } + } + visit(node) +} diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..a003d72 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,15 @@ +# Catalog Release Procedure + +This repository releases source tags only. It has no generated artifacts. + +Before creating a semantic version tag, start from a clean, synchronized +`main` branch. Confirm that no Go workspace, `replace` directive, or `vendor` +tree is active, then run `GOWORK=off go test ./...`, `GOWORK=off go test -race +./...`, `GOWORK=off go vet ./...`, `GOWORK=off go build ./...`, formatting, +repository-hygiene, credential, and ordinary module-resolution checks. + +Create an annotated tag only after that validation succeeds. Do not create or +move an existing tag. Push `main`, push only the selected tag, and verify the +remote tag identifies the intended commit. Finally, from a temporary module +with `GOWORK=off` and no replacement, resolve the published module version and +verify the resolved version is the selected tag. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..47c8455 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitea.maximumdirect.net/eric/promptkit-backend-openrouter + +go 1.25.5 + +require gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a62c313 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=