Last active
October 11, 2026 03:16
-
-
Save m13253/40a8f5255c1994ce813317767c61420d to your computer and use it in GitHub Desktop.
Convert Floway model list to oh-my-pi, Pi, VSCode, and Zed settings files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Usage: | |
| # curl --oauth2-bearer '<API_KEY>' http://localhost:18088/v1/models | ./floway-to-omp.py [NAME] [API_URL] | |
| # defaults: NAME=Floway, API_URL=http://localhost:18088/v1 | |
| # | |
| # Installation: | |
| # Pi: Install to ~/.pi/agent/models.json | |
| # Oh-my-pi: Install to ~/.omp/agent/models.json and will be automatically converted to models.yml | |
| # Then, inside Pi or oh-my-pi, type /login to configure your API key. | |
| # | |
| # Reference: | |
| # https://pi.dev/docs/latest/models | |
| # https://github.com/can1357/oh-my-pi/blob/main/docs/models.md | |
| import decimal | |
| import json | |
| import sys | |
| DEFAULT_UPSTREAM_NAME = "Floway" | |
| DEFAULT_API_URL = "http://localhost:18088/v1" | |
| def model_config(model): | |
| chat = model.get("chat", {}) | |
| limits = model.get("limits", {}) | |
| rates = {} | |
| for entry in model.get("pricing", {}).get("entries", []): | |
| if not entry.get("selector"): | |
| rates = entry.get("rates", {}) | |
| break | |
| config = { | |
| "id": model["id"], | |
| "name": model.get("display_name", model["id"]), | |
| "reasoning": True, # Force True until we fix Floway's metadata discovery | |
| "thinkingLevelMap": { | |
| "xhigh": "xhigh", | |
| "max": "max", | |
| }, # Force all possible reasoning efforts | |
| } | |
| if "input" in (modalities := chat.get("modalities", {})): | |
| config["input"] = modalities["input"] | |
| if "max_context_window_tokens" in limits: | |
| config["contextWindow"] = limits["max_context_window_tokens"] | |
| if "max_output_tokens" in limits: | |
| config["maxTokens"] = limits["max_output_tokens"] | |
| if ( | |
| "input_tokens" in rates | |
| or "output_tokens" in rates | |
| or "input_cache_read_tokens" in rates | |
| or "input_cache_write_tokens" in rates | |
| ): | |
| config["cost"] = { | |
| "input": float(decimal.Decimal(rates.get("input_tokens", 0)).scaleb(6)), | |
| "output": float(decimal.Decimal(rates.get("output_tokens", 0)).scaleb(6)), | |
| "cacheRead": float( | |
| decimal.Decimal(rates.get("input_cache_read_tokens", 0)).scaleb(6) | |
| ), | |
| "cacheWrite": float( | |
| decimal.Decimal(rates.get("input_cache_write_tokens", 0)).scaleb(6) | |
| ), | |
| } | |
| config["compat"] = { | |
| "supportsStore": True, | |
| } | |
| return config | |
| def main(): | |
| upstream = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_UPSTREAM_NAME | |
| api_url = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_API_URL | |
| models = ( | |
| m | |
| for m in json.load(sys.stdin.buffer)["data"] | |
| if m.get("type") == "model" and m.get("kind") == "chat" | |
| ) | |
| settings = { | |
| "providers": { | |
| upstream: { | |
| "baseUrl": api_url, | |
| "api": "openai-responses", | |
| "authHeader": True, | |
| "models": [model_config(m) for m in models], | |
| } | |
| } | |
| } | |
| print(json.dumps(settings, ensure_ascii=False, indent=4)) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Usage: | |
| # curl --oauth2-bearer '<API_KEY>' http://localhost:18088/v1/models | ./floway-to-vscode.py [NAME] [API_URL] | |
| # defaults: NAME=Floway, API_URL=http://localhost:18088/v1 | |
| # | |
| # Installation: | |
| # 1. Ctrl-Shift-P (Cmd-Shift-P): “Chat: Open Language Models (JSON)”, paste in the generated JSON | |
| # 2. Ctrl-Shift-P (Cmd-Shift-P): “Developer: Reload Window” | |
| # 3. Ctrl-Shift-P (Cmd-Shift-P): “Chat: Manage Language Models” | |
| # 4. Right click a model, and choose “Update API Key” | |
| # | |
| # Reference: | |
| # https://code.visualstudio.com/docs/agent-customization/language-models#_model-configuration-reference | |
| import json | |
| import sys | |
| DEFAULT_UPSTREAM_NAME = "Floway" | |
| DEFAULT_API_URL = "http://localhost:18088/v1" | |
| DEFAULT_CONTEXT_WINDOW = 262144 | |
| DEFAULT_MAX_OUTPUT = 65536 | |
| SECRET_ID_PLACEHOLDER = "${input:chat.lm.secret.REPLACE_WITH_YOUR_SECRET_ID}" | |
| def model_config(model, api_url): | |
| chat = model.get("chat", {}) | |
| limits = model.get("limits", {}) | |
| max_context_window_tokens = limits.get("max_context_window_tokens") | |
| max_input_tokens = limits.get("max_prompt_tokens") | |
| max_output_tokens = limits.get("max_output_tokens") | |
| match ( | |
| max_context_window_tokens is not None, | |
| max_input_tokens is not None, | |
| max_output_tokens is not None, | |
| ): | |
| case False, False, False: | |
| max_input_tokens = DEFAULT_CONTEXT_WINDOW - DEFAULT_MAX_OUTPUT | |
| max_output_tokens = DEFAULT_MAX_OUTPUT | |
| case False, False, True: | |
| max_input_tokens = max( | |
| DEFAULT_CONTEXT_WINDOW - max_output_tokens, max_output_tokens | |
| ) | |
| case False, True, False: | |
| max_output_tokens = max(max_input_tokens, DEFAULT_MAX_OUTPUT) | |
| case _, True, True: | |
| pass | |
| case True, False, False: | |
| max_output_tokens = min(max_context_window_tokens // 2, DEFAULT_MAX_OUTPUT) | |
| max_input_tokens = max_context_window_tokens - max_output_tokens | |
| case True, False, True: | |
| max_input_tokens = max_context_window_tokens - max_output_tokens | |
| case True, True, False: | |
| max_output_tokens = max_context_window_tokens - max_input_tokens | |
| config = { | |
| "id": model["id"], | |
| "name": model.get("display_name", model["id"]), | |
| "url": api_url.rstrip("/") + "/responses", | |
| "toolCalling": True, | |
| "vision": "image" in chat.get("modalities", {}).get("input", []), | |
| "thinking": True, # Force True until we fix Floway's metadata discovery | |
| "maxInputTokens": max_input_tokens, | |
| "maxOutputTokens": max_output_tokens, | |
| } | |
| if "reasoning" in chat: | |
| config["reasoningEffortFormat"] = "responses" | |
| if (effort := chat["reasoning"].get("effort")).get("supported"): | |
| config["supportsReasoningEffort"] = list(effort["supported"]) | |
| return config | |
| def main(): | |
| upstream = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_UPSTREAM_NAME | |
| api_url = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_API_URL | |
| models = ( | |
| m | |
| for m in json.load(sys.stdin.buffer)["data"] | |
| if m.get("type") == "model" and m.get("kind") == "chat" | |
| ) | |
| settings = [ | |
| { | |
| "name": upstream, | |
| "vendor": "customendpoint", | |
| "apiKey": SECRET_ID_PLACEHOLDER, | |
| "apiType": "responses", | |
| "models": [model_config(m, api_url) for m in models], | |
| } | |
| ] | |
| print(json.dumps(settings, ensure_ascii=False, indent=4)) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Usage: | |
| # curl --oauth2-bearer '<API_KEY>' http://localhost:18088/v1/models | ./floway-to-zed.py [NAME] [API_URL] | |
| # defaults: NAME=Floway, API_URL=http://localhost:18088/v1 | |
| # | |
| # Install to ~/.config/zed/global_settings.json | |
| # Then, go to Zed Settings → User → AI → General → LLM Providers to configure your API key. | |
| # | |
| # Reference: | |
| # https://zed.dev/docs/ai/use-api-access#openai-compatible | |
| # zed://schemas/settings | |
| import json | |
| import sys | |
| DEFAULT_UPSTREAM_NAME = "Floway" | |
| DEFAULT_API_URL = "http://localhost:18088/v1" | |
| DEFAULT_CONTEXT_WINDOW = 262144 | |
| def model_config(model): | |
| chat = model.get("chat", {}) | |
| limits = model.get("limits", {}) | |
| config = { | |
| "name": model["id"], | |
| "display_name": model.get("display_name", model["id"]), | |
| "max_tokens": limits.get("max_context_window_tokens", DEFAULT_CONTEXT_WINDOW), | |
| "capabilities": { | |
| "tools": True, | |
| "images": "image" in chat.get("modalities", {}).get("input", []), | |
| "parallel_tool_calls": True, | |
| "prompt_cache_key": True, | |
| "chat_completions": False, | |
| "interleaved_reasoning": True, | |
| }, | |
| } | |
| return config | |
| def main(): | |
| upstream = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_UPSTREAM_NAME | |
| api_url = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_API_URL | |
| models = ( | |
| m | |
| for m in json.load(sys.stdin.buffer)["data"] | |
| if m.get("type") == "model" and m.get("kind") == "chat" | |
| ) | |
| settings = { | |
| "language_models": { | |
| "openai_compatible": { | |
| upstream: { | |
| "api_url": api_url, | |
| "available_models": [model_config(m) for m in models], | |
| } | |
| } | |
| } | |
| } | |
| print(json.dumps(settings, ensure_ascii=False, indent=2)) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment