diff options
| -rw-r--r-- | LICENSE | 18 | ||||
| -rw-r--r-- | README.md | 63 | ||||
| -rw-r--r-- | _selfcheck.lua | 106 | ||||
| -rw-r--r-- | init.lua | 60 | ||||
| -rw-r--r-- | panto-opencode-go-scm-1.rockspec | 19 |
5 files changed, 266 insertions, 0 deletions
@@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2026 Travis Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d36bd73 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# panto-opencode-go + +OpenCode Go defaults for panto. Mark named tables; the extension fills the boilerplate. + +## Setup + +Requires panto's `configure(config)` extension hook (before auth/provider validation). + +```toml +[extensions] +rocks = ["panto-opencode-go"] + +[auth.opencode] +panto_opencode_go_extension = true + +[providers.go] +auth = "opencode" +panto_opencode_go_extension = true +``` + +```sh +export OPENCODE_API_KEY='your-key' +panto models sync +panto models list go: +``` + +## Defaults + +| Setting | Value | +| --- | --- | +| Auth | API key via `${env:OPENCODE_API_KEY}`; no OAuth login | +| APIs | Chat Completions (default), Responses, Anthropic Messages | +| OpenAI endpoint | `https://opencode.ai/zen/go/v1` | +| Anthropic endpoint | `https://opencode.ai/zen/go` | +| Catalog | `opencode-go` | +| Headers | `User-Agent: panto`, `x-opencode-session: ${session:id}` | + +## Rules + +- Only `panto_opencode_go_extension = true` opts in; auth/provider names are arbitrary. +- Keep `providers.<name>.auth` explicit; the extension never guesses linkage. +- Explicit values win, including nested endpoints and case-insensitive headers. +- Defaults are independent per table/load; no config files are rewritten. +- No network or credentials in Lua; env/session placeholders stay raw for panto. +- Rigs must include this extension themselves; `--no-extensions` skips it. + +Existing flat `base_url` configurations stay flat; otherwise all three endpoints +are filled. Overrides must remain valid panto configuration. + +## Packaging / checks + +```sh +panto lua _selfcheck.lua +luarocks lint panto-opencode-go-scm-1.rockspec +``` + +The rockspec maps `require("panto-opencode-go")` to `init.lua`; no dependencies +beyond Lua. + +## References + +- [OpenCode Go](https://opencode.ai/docs/go/) +- Defaults match the existing panto OpenCode Go configuration. diff --git a/_selfcheck.lua b/_selfcheck.lua new file mode 100644 index 0000000..ec3b4ee --- /dev/null +++ b/_selfcheck.lua @@ -0,0 +1,106 @@ +-- Offline: panto lua _selfcheck.lua (or Lua 5.4). +local here = debug.getinfo(1, "S").source:match("^@(.*)/[^/]+$") or "." +local entry = assert(loadfile(here .. "/init.lua"))() +assert(entry.name == "opencode-go" and type(entry.configure) == "function") +assert(entry.activate == nil, "configure-only extension") + +local function contains(actual, expected) + for key, value in pairs(expected) do + if type(value) == "table" then + assert(type(actual[key]) == "table", key .. " must be a table") + contains(actual[key], value) + else + assert(actual[key] == value, key .. " default mismatch") + end + end +end + +local empty = {} +entry.configure(empty) +assert(next(empty) == nil, "no implicit auth/providers") + +local untouched = { providers = { + absent = {}, disabled = { panto_opencode_go_extension = false }, + string_marker = { panto_opencode_go_extension = "true" }, +}, auth = { absent = {}, disabled = { panto_opencode_go_extension = false } } } +entry.configure(untouched) +assert(next(untouched.providers.absent) == nil) +assert(untouched.providers.disabled.style == nil and untouched.providers.string_marker.style == nil) +assert(next(untouched.auth.absent) == nil and untouched.auth.disabled.type == nil) + +local config = { + auth = { first = { panto_opencode_go_extension = true }, second = { panto_opencode_go_extension = true } }, + providers = { + first = { panto_opencode_go_extension = true, auth = "second" }, + second = { panto_opencode_go_extension = true, auth = "first" }, + }, +} +entry.configure(config) +contains(config.auth.first, { + type = "api_key", + key = "${env:OPENCODE_API_KEY}", +}) +contains(config.providers.first, { + style = "openai_chat", + model_catalog_name = "opencode-go", + extra_headers = { + ["User-Agent"] = "panto", + ["x-opencode-session"] = "${session:id}", + }, + endpoints = { + openai_chat = { base_url = "https://opencode.ai/zen/go/v1" }, + openai_responses = { base_url = "https://opencode.ai/zen/go/v1" }, + anthropic_messages = { base_url = "https://opencode.ai/zen/go" }, + }, +}) +assert(config.providers.first.auth == "second" and config.providers.second.auth == "first") +assert(config.providers.first.extra_headers ~= config.providers.second.extra_headers) +config.providers.first.extra_headers.custom = "first-only" +assert(config.providers.second.extra_headers.custom == nil, "instance defaults must not alias") +entry.configure(config) +assert(config.providers.first.extra_headers.custom == "first-only", "repeatable configuration") + +local fresh = { providers = { third = { panto_opencode_go_extension = true } } } +entry.configure(fresh) +assert(fresh.providers.third.auth == nil, "never invent auth linkage") +assert(fresh.providers.third.extra_headers.custom == nil, "no cross-load mutable defaults") + +local override = { + auth = { login = { panto_opencode_go_extension = true, client_id = "custom-client", key = "${env:CUSTOM_KEY}" } }, + providers = { custom = { + panto_opencode_go_extension = true, + auth = "login", + model_catalog_name = "custom-catalog", + extra_headers = { ["user-agent"] = "custom-header", custom = "${session:id}" }, + endpoints = { openai_responses = { + base_url = "https://gateway.example/v1", + extra_headers = { local_header = "kept" }, + } }, + } }, +} +entry.configure(override) +local custom = override.providers.custom +assert(override.auth.login.client_id == "custom-client") +assert(override.auth.login.key == "${env:CUSTOM_KEY}", "raw key reference preserved") +assert(custom.auth == "login" and custom.model_catalog_name == "custom-catalog") +assert(custom.base_url == nil and custom.dialect == nil, "endpoint form stays valid") +assert(custom.endpoints.openai_responses.base_url == "https://gateway.example/v1") +assert(custom.endpoints.openai_responses.extra_headers.local_header == "kept") +assert(custom.extra_headers["user-agent"] == "custom-header") +assert(custom.extra_headers["User-Agent"] == nil, "case-insensitive header override") +assert(custom.extra_headers.custom == "${session:id}", "raw session reference preserved") + +local flat = { providers = { custom = { + panto_opencode_go_extension = true, auth = "login", style = "openai_chat", + base_url = "https://gateway.example/v1", +} } } +entry.configure(flat) +assert(flat.providers.custom.endpoints == nil, "explicit flat provider stays flat") +assert(flat.providers.custom.style == "openai_chat") +assert(flat.providers.custom.base_url == "https://gateway.example/v1") + +local invalid = { providers = { marked = { panto_opencode_go_extension = true, extra_headers = false } } } +entry.configure(invalid) +assert(invalid.providers.marked.extra_headers == false, "explicit false remains untouched") + +print("panto-opencode-go: ok") diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..2444c46 --- /dev/null +++ b/init.lua @@ -0,0 +1,60 @@ +-- OpenCode Go defaults for panto. No login, networking, or session activation. +local auth_defaults = { + type = "api_key", + key = "${env:OPENCODE_API_KEY}", +} + +local provider_defaults = { + style = "openai_chat", + model_catalog_name = "opencode-go", + extra_headers = { + ["User-Agent"] = "panto", + ["x-opencode-session"] = "${session:id}", + }, +} + +local endpoint_defaults = { + openai_chat = { base_url = "https://opencode.ai/zen/go/v1" }, + openai_responses = { base_url = "https://opencode.ai/zen/go/v1" }, + anthropic_messages = { base_url = "https://opencode.ai/zen/go" }, +} + +-- Fill only missing values; HTTP header names are case-insensitive. +local function fill_missing(target, defaults, headers) + for key, value in pairs(defaults) do + local target_key = key + if headers then + for existing in pairs(target) do + if type(existing) == "string" and existing:lower() == key:lower() then + target_key = existing + break + end + end + end + if target[target_key] == nil then + target[target_key] = type(value) == "table" and {} or value + end + if type(value) == "table" and type(target[target_key]) == "table" then + fill_missing(target[target_key], value, key == "extra_headers") + end + end +end + +return { + name = "opencode-go", + configure = function(config) + for _, auth in pairs(config.auth or {}) do + if type(auth) == "table" and auth.panto_opencode_go_extension == true then + fill_missing(auth, auth_defaults) + end + end + for _, provider in pairs(config.providers or {}) do + if type(provider) == "table" and provider.panto_opencode_go_extension == true then + fill_missing(provider, provider_defaults) + if provider.base_url == nil then + fill_missing(provider, { endpoints = endpoint_defaults }) + end + end + end + end, +} diff --git a/panto-opencode-go-scm-1.rockspec b/panto-opencode-go-scm-1.rockspec new file mode 100644 index 0000000..3c1ebde --- /dev/null +++ b/panto-opencode-go-scm-1.rockspec @@ -0,0 +1,19 @@ +rockspec_format = "3.0" +package = "panto-opencode-go" +version = "scm-1" + +source = { + url = "git+https://code.tjp.lol/public/panto-opencode-go.git", +} + +description = { + summary = "OpenCode Go defaults for panto", + license = "MIT", +} + +dependencies = { "lua >= 5.4, < 5.5" } + +build = { + type = "builtin", + modules = { ["panto-opencode-go"] = "init.lua" }, +} |
