From 34d438b11b506bdc87eb44d60ad0424cd1e8f5b0 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Sun, 22 Feb 2026 18:26:13 +0100 Subject: [PATCH 01/15] chore(gom-25): generate docs --- cmd/gomodel/docs/docs.go | 746 ++++++++++++++++++++++++++++++++++ cmd/gomodel/docs/swagger.json | 722 ++++++++++++++++++++++++++++++++ cmd/gomodel/docs/swagger.yaml | 472 +++++++++++++++++++++ cmd/gomodel/main.go | 8 + go.mod | 22 +- go.sum | 68 +++- internal/admin/handler.go | 30 ++ internal/server/handlers.go | 41 ++ internal/server/http.go | 5 + 9 files changed, 2100 insertions(+), 14 deletions(-) create mode 100644 cmd/gomodel/docs/docs.go create mode 100644 cmd/gomodel/docs/swagger.json create mode 100644 cmd/gomodel/docs/swagger.yaml diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go new file mode 100644 index 00000000..7b3c7b08 --- /dev/null +++ b/cmd/gomodel/docs/docs.go @@ -0,0 +1,746 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/admin/api/v1/models": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List all registered models with provider info", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/providers.ModelWithProvider" + } + } + } + } + } + }, + "/admin/api/v1/usage/daily": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get usage breakdown by period", + "parameters": [ + { + "type": "integer", + "description": "Number of days (default 30)", + "name": "days", + "in": "query" + }, + { + "type": "string", + "description": "Start date (YYYY-MM-DD)", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date (YYYY-MM-DD)", + "name": "end_date", + "in": "query" + }, + { + "type": "string", + "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)", + "name": "interval", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/usage.DailyUsage" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/admin/api/v1/usage/summary": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get usage summary", + "parameters": [ + { + "type": "integer", + "description": "Number of days (default 30)", + "name": "days", + "in": "query" + }, + { + "type": "string", + "description": "Start date (YYYY-MM-DD)", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date (YYYY-MM-DD)", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/usage.UsageSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/health": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "Health check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/v1/chat/completions": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "chat" + ], + "summary": "Create a chat completion", + "parameters": [ + { + "description": "Chat completion request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.ChatRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ChatResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/v1/models": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "models" + ], + "summary": "List available models", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ModelsResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/v1/responses": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "responses" + ], + "summary": "Create a model response (Responses API)", + "parameters": [ + { + "description": "Responses API request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.ResponsesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ResponsesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + } + }, + "definitions": { + "core.ChatRequest": { + "type": "object", + "properties": { + "max_tokens": { + "type": "integer" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Message" + } + }, + "model": { + "type": "string" + }, + "reasoning": { + "$ref": "#/definitions/core.Reasoning" + }, + "stream": { + "type": "boolean" + }, + "stream_options": { + "$ref": "#/definitions/core.StreamOptions" + }, + "temperature": { + "type": "number" + } + } + }, + "core.ChatResponse": { + "type": "object", + "properties": { + "choices": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Choice" + } + }, + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "usage": { + "$ref": "#/definitions/core.Usage" + } + } + }, + "core.Choice": { + "type": "object", + "properties": { + "finish_reason": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "message": { + "$ref": "#/definitions/core.Message" + } + } + }, + "core.ErrorType": { + "type": "string", + "enum": [ + "provider_error", + "rate_limit_error", + "invalid_request_error", + "authentication_error", + "not_found_error" + ], + "x-enum-varnames": [ + "ErrorTypeProvider", + "ErrorTypeRateLimit", + "ErrorTypeInvalidRequest", + "ErrorTypeAuthentication", + "ErrorTypeNotFound" + ] + }, + "core.GatewayError": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "status_code": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/core.ErrorType" + } + } + }, + "core.Message": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "core.Model": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "object": { + "type": "string" + }, + "owned_by": { + "type": "string" + } + } + }, + "core.ModelsResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Model" + } + }, + "object": { + "type": "string" + } + } + }, + "core.Reasoning": { + "type": "object", + "properties": { + "effort": { + "description": "Effort controls how much reasoning effort the model should use.\nValid values are \"low\", \"medium\", and \"high\".", + "type": "string" + } + } + }, + "core.ResponsesContentItem": { + "type": "object", + "properties": { + "annotations": { + "type": "array", + "items": { + "type": "string" + } + }, + "text": { + "type": "string" + }, + "type": { + "description": "\"output_text\", etc.", + "type": "string" + } + } + }, + "core.ResponsesError": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "core.ResponsesOutputItem": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/core.ResponsesContentItem" + } + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "description": "\"message\", \"function_call\", etc.", + "type": "string" + } + } + }, + "core.ResponsesRequest": { + "type": "object", + "properties": { + "input": { + "description": "Can be string or []ResponsesInputItem" + }, + "instructions": { + "type": "string" + }, + "max_output_tokens": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "model": { + "type": "string" + }, + "reasoning": { + "$ref": "#/definitions/core.Reasoning" + }, + "stream": { + "type": "boolean" + }, + "stream_options": { + "$ref": "#/definitions/core.StreamOptions" + }, + "temperature": { + "type": "number" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + }, + "core.ResponsesResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "error": { + "$ref": "#/definitions/core.ResponsesError" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "description": "\"response\"", + "type": "string" + }, + "output": { + "type": "array", + "items": { + "$ref": "#/definitions/core.ResponsesOutputItem" + } + }, + "provider": { + "type": "string" + }, + "status": { + "description": "\"completed\", \"failed\", \"in_progress\"", + "type": "string" + }, + "usage": { + "$ref": "#/definitions/core.ResponsesUsage" + } + } + }, + "core.ResponsesUsage": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer" + }, + "output_tokens": { + "type": "integer" + }, + "raw_usage": { + "type": "object", + "additionalProperties": {} + }, + "total_tokens": { + "type": "integer" + } + } + }, + "core.StreamOptions": { + "type": "object", + "properties": { + "include_usage": { + "description": "IncludeUsage requests token usage information in streaming responses.\nWhen true, the final streaming chunk will include usage statistics.", + "type": "boolean" + } + } + }, + "core.Usage": { + "type": "object", + "properties": { + "completion_tokens": { + "type": "integer" + }, + "prompt_tokens": { + "type": "integer" + }, + "raw_usage": { + "type": "object", + "additionalProperties": {} + }, + "total_tokens": { + "type": "integer" + } + } + }, + "providers.ModelWithProvider": { + "type": "object", + "properties": { + "model": { + "$ref": "#/definitions/core.Model" + }, + "provider_type": { + "type": "string" + } + } + }, + "usage.DailyUsage": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "input_tokens": { + "type": "integer" + }, + "output_tokens": { + "type": "integer" + }, + "requests": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "usage.UsageSummary": { + "type": "object", + "properties": { + "total_input_tokens": { + "type": "integer" + }, + "total_output_tokens": { + "type": "integer" + }, + "total_requests": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/", + Schemes: []string{}, + Title: "GOModel API", + Description: "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json new file mode 100644 index 00000000..2ed4574b --- /dev/null +++ b/cmd/gomodel/docs/swagger.json @@ -0,0 +1,722 @@ +{ + "swagger": "2.0", + "info": { + "description": "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API.", + "title": "GOModel API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/", + "paths": { + "/admin/api/v1/models": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List all registered models with provider info", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/providers.ModelWithProvider" + } + } + } + } + } + }, + "/admin/api/v1/usage/daily": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get usage breakdown by period", + "parameters": [ + { + "type": "integer", + "description": "Number of days (default 30)", + "name": "days", + "in": "query" + }, + { + "type": "string", + "description": "Start date (YYYY-MM-DD)", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date (YYYY-MM-DD)", + "name": "end_date", + "in": "query" + }, + { + "type": "string", + "description": "Grouping interval: daily, weekly, monthly, yearly (default daily)", + "name": "interval", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/usage.DailyUsage" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/admin/api/v1/usage/summary": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get usage summary", + "parameters": [ + { + "type": "integer", + "description": "Number of days (default 30)", + "name": "days", + "in": "query" + }, + { + "type": "string", + "description": "Start date (YYYY-MM-DD)", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date (YYYY-MM-DD)", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/usage.UsageSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/health": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "Health check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/v1/chat/completions": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "chat" + ], + "summary": "Create a chat completion", + "parameters": [ + { + "description": "Chat completion request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.ChatRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ChatResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/v1/models": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "models" + ], + "summary": "List available models", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ModelsResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + }, + "/v1/responses": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "responses" + ], + "summary": "Create a model response (Responses API)", + "parameters": [ + { + "description": "Responses API request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.ResponsesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.ResponsesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "429": { + "description": "Too Many Requests", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } + } + } + } + } + }, + "definitions": { + "core.ChatRequest": { + "type": "object", + "properties": { + "max_tokens": { + "type": "integer" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Message" + } + }, + "model": { + "type": "string" + }, + "reasoning": { + "$ref": "#/definitions/core.Reasoning" + }, + "stream": { + "type": "boolean" + }, + "stream_options": { + "$ref": "#/definitions/core.StreamOptions" + }, + "temperature": { + "type": "number" + } + } + }, + "core.ChatResponse": { + "type": "object", + "properties": { + "choices": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Choice" + } + }, + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "usage": { + "$ref": "#/definitions/core.Usage" + } + } + }, + "core.Choice": { + "type": "object", + "properties": { + "finish_reason": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "message": { + "$ref": "#/definitions/core.Message" + } + } + }, + "core.ErrorType": { + "type": "string", + "enum": [ + "provider_error", + "rate_limit_error", + "invalid_request_error", + "authentication_error", + "not_found_error" + ], + "x-enum-varnames": [ + "ErrorTypeProvider", + "ErrorTypeRateLimit", + "ErrorTypeInvalidRequest", + "ErrorTypeAuthentication", + "ErrorTypeNotFound" + ] + }, + "core.GatewayError": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "status_code": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/core.ErrorType" + } + } + }, + "core.Message": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "core.Model": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "object": { + "type": "string" + }, + "owned_by": { + "type": "string" + } + } + }, + "core.ModelsResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/core.Model" + } + }, + "object": { + "type": "string" + } + } + }, + "core.Reasoning": { + "type": "object", + "properties": { + "effort": { + "description": "Effort controls how much reasoning effort the model should use.\nValid values are \"low\", \"medium\", and \"high\".", + "type": "string" + } + } + }, + "core.ResponsesContentItem": { + "type": "object", + "properties": { + "annotations": { + "type": "array", + "items": { + "type": "string" + } + }, + "text": { + "type": "string" + }, + "type": { + "description": "\"output_text\", etc.", + "type": "string" + } + } + }, + "core.ResponsesError": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "core.ResponsesOutputItem": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/core.ResponsesContentItem" + } + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "description": "\"message\", \"function_call\", etc.", + "type": "string" + } + } + }, + "core.ResponsesRequest": { + "type": "object", + "properties": { + "input": { + "description": "Can be string or []ResponsesInputItem" + }, + "instructions": { + "type": "string" + }, + "max_output_tokens": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "model": { + "type": "string" + }, + "reasoning": { + "$ref": "#/definitions/core.Reasoning" + }, + "stream": { + "type": "boolean" + }, + "stream_options": { + "$ref": "#/definitions/core.StreamOptions" + }, + "temperature": { + "type": "number" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + }, + "core.ResponsesResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "error": { + "$ref": "#/definitions/core.ResponsesError" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "description": "\"response\"", + "type": "string" + }, + "output": { + "type": "array", + "items": { + "$ref": "#/definitions/core.ResponsesOutputItem" + } + }, + "provider": { + "type": "string" + }, + "status": { + "description": "\"completed\", \"failed\", \"in_progress\"", + "type": "string" + }, + "usage": { + "$ref": "#/definitions/core.ResponsesUsage" + } + } + }, + "core.ResponsesUsage": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer" + }, + "output_tokens": { + "type": "integer" + }, + "raw_usage": { + "type": "object", + "additionalProperties": {} + }, + "total_tokens": { + "type": "integer" + } + } + }, + "core.StreamOptions": { + "type": "object", + "properties": { + "include_usage": { + "description": "IncludeUsage requests token usage information in streaming responses.\nWhen true, the final streaming chunk will include usage statistics.", + "type": "boolean" + } + } + }, + "core.Usage": { + "type": "object", + "properties": { + "completion_tokens": { + "type": "integer" + }, + "prompt_tokens": { + "type": "integer" + }, + "raw_usage": { + "type": "object", + "additionalProperties": {} + }, + "total_tokens": { + "type": "integer" + } + } + }, + "providers.ModelWithProvider": { + "type": "object", + "properties": { + "model": { + "$ref": "#/definitions/core.Model" + }, + "provider_type": { + "type": "string" + } + } + }, + "usage.DailyUsage": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "input_tokens": { + "type": "integer" + }, + "output_tokens": { + "type": "integer" + }, + "requests": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "usage.UsageSummary": { + "type": "object", + "properties": { + "total_input_tokens": { + "type": "integer" + }, + "total_output_tokens": { + "type": "integer" + }, + "total_requests": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/cmd/gomodel/docs/swagger.yaml b/cmd/gomodel/docs/swagger.yaml new file mode 100644 index 00000000..82c01d5a --- /dev/null +++ b/cmd/gomodel/docs/swagger.yaml @@ -0,0 +1,472 @@ +basePath: / +definitions: + core.ChatRequest: + properties: + max_tokens: + type: integer + messages: + items: + $ref: '#/definitions/core.Message' + type: array + model: + type: string + reasoning: + $ref: '#/definitions/core.Reasoning' + stream: + type: boolean + stream_options: + $ref: '#/definitions/core.StreamOptions' + temperature: + type: number + type: object + core.ChatResponse: + properties: + choices: + items: + $ref: '#/definitions/core.Choice' + type: array + created: + type: integer + id: + type: string + model: + type: string + object: + type: string + provider: + type: string + usage: + $ref: '#/definitions/core.Usage' + type: object + core.Choice: + properties: + finish_reason: + type: string + index: + type: integer + message: + $ref: '#/definitions/core.Message' + type: object + core.ErrorType: + enum: + - provider_error + - rate_limit_error + - invalid_request_error + - authentication_error + - not_found_error + type: string + x-enum-varnames: + - ErrorTypeProvider + - ErrorTypeRateLimit + - ErrorTypeInvalidRequest + - ErrorTypeAuthentication + - ErrorTypeNotFound + core.GatewayError: + properties: + message: + type: string + provider: + type: string + status_code: + type: integer + type: + $ref: '#/definitions/core.ErrorType' + type: object + core.Message: + properties: + content: + type: string + role: + type: string + type: object + core.Model: + properties: + created: + type: integer + id: + type: string + object: + type: string + owned_by: + type: string + type: object + core.ModelsResponse: + properties: + data: + items: + $ref: '#/definitions/core.Model' + type: array + object: + type: string + type: object + core.Reasoning: + properties: + effort: + description: |- + Effort controls how much reasoning effort the model should use. + Valid values are "low", "medium", and "high". + type: string + type: object + core.ResponsesContentItem: + properties: + annotations: + items: + type: string + type: array + text: + type: string + type: + description: '"output_text", etc.' + type: string + type: object + core.ResponsesError: + properties: + code: + type: string + message: + type: string + type: object + core.ResponsesOutputItem: + properties: + content: + items: + $ref: '#/definitions/core.ResponsesContentItem' + type: array + id: + type: string + role: + type: string + status: + type: string + type: + description: '"message", "function_call", etc.' + type: string + type: object + core.ResponsesRequest: + properties: + input: + description: Can be string or []ResponsesInputItem + instructions: + type: string + max_output_tokens: + type: integer + metadata: + additionalProperties: + type: string + type: object + model: + type: string + reasoning: + $ref: '#/definitions/core.Reasoning' + stream: + type: boolean + stream_options: + $ref: '#/definitions/core.StreamOptions' + temperature: + type: number + tools: + items: + additionalProperties: {} + type: object + type: array + type: object + core.ResponsesResponse: + properties: + created_at: + type: integer + error: + $ref: '#/definitions/core.ResponsesError' + id: + type: string + model: + type: string + object: + description: '"response"' + type: string + output: + items: + $ref: '#/definitions/core.ResponsesOutputItem' + type: array + provider: + type: string + status: + description: '"completed", "failed", "in_progress"' + type: string + usage: + $ref: '#/definitions/core.ResponsesUsage' + type: object + core.ResponsesUsage: + properties: + input_tokens: + type: integer + output_tokens: + type: integer + raw_usage: + additionalProperties: {} + type: object + total_tokens: + type: integer + type: object + core.StreamOptions: + properties: + include_usage: + description: |- + IncludeUsage requests token usage information in streaming responses. + When true, the final streaming chunk will include usage statistics. + type: boolean + type: object + core.Usage: + properties: + completion_tokens: + type: integer + prompt_tokens: + type: integer + raw_usage: + additionalProperties: {} + type: object + total_tokens: + type: integer + type: object + providers.ModelWithProvider: + properties: + model: + $ref: '#/definitions/core.Model' + provider_type: + type: string + type: object + usage.DailyUsage: + properties: + date: + type: string + input_tokens: + type: integer + output_tokens: + type: integer + requests: + type: integer + total_tokens: + type: integer + type: object + usage.UsageSummary: + properties: + total_input_tokens: + type: integer + total_output_tokens: + type: integer + total_requests: + type: integer + total_tokens: + type: integer + type: object +host: localhost:8080 +info: + contact: {} + description: High-performance AI gateway routing requests to multiple LLM providers + (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API. + title: GOModel API + version: "1.0" +paths: + /admin/api/v1/models: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/providers.ModelWithProvider' + type: array + security: + - BearerAuth: [] + summary: List all registered models with provider info + tags: + - admin + /admin/api/v1/usage/daily: + get: + parameters: + - description: Number of days (default 30) + in: query + name: days + type: integer + - description: Start date (YYYY-MM-DD) + in: query + name: start_date + type: string + - description: End date (YYYY-MM-DD) + in: query + name: end_date + type: string + - description: 'Grouping interval: daily, weekly, monthly, yearly (default daily)' + in: query + name: interval + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/usage.DailyUsage' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/core.GatewayError' + security: + - BearerAuth: [] + summary: Get usage breakdown by period + tags: + - admin + /admin/api/v1/usage/summary: + get: + parameters: + - description: Number of days (default 30) + in: query + name: days + type: integer + - description: Start date (YYYY-MM-DD) + in: query + name: start_date + type: string + - description: End date (YYYY-MM-DD) + in: query + name: end_date + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/usage.UsageSummary' + "400": + description: Bad Request + schema: + $ref: '#/definitions/core.GatewayError' + security: + - BearerAuth: [] + summary: Get usage summary + tags: + - admin + /health: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + summary: Health check + tags: + - system + /v1/chat/completions: + post: + consumes: + - application/json + parameters: + - description: Chat completion request + in: body + name: request + required: true + schema: + $ref: '#/definitions/core.ChatRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/core.ChatResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/core.GatewayError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/core.GatewayError' + "429": + description: Too Many Requests + schema: + $ref: '#/definitions/core.GatewayError' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/core.GatewayError' + security: + - BearerAuth: [] + summary: Create a chat completion + tags: + - chat + /v1/models: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/core.ModelsResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/core.GatewayError' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/core.GatewayError' + security: + - BearerAuth: [] + summary: List available models + tags: + - models + /v1/responses: + post: + consumes: + - application/json + parameters: + - description: Responses API request + in: body + name: request + required: true + schema: + $ref: '#/definitions/core.ResponsesRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/core.ResponsesResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/core.GatewayError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/core.GatewayError' + "429": + description: Too Many Requests + schema: + $ref: '#/definitions/core.GatewayError' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/core.GatewayError' + security: + - BearerAuth: [] + summary: Create a model response (Responses API) + tags: + - responses +securityDefinitions: + BearerAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index 2e75ec59..db5d0e4f 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -28,6 +28,14 @@ import ( "golang.org/x/term" ) +// @title GOModel API +// @version 1.0 +// @description High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API. +// @host localhost:8080 +// @BasePath / +// @securityDefinitions.apikey BearerAuth +// @in header +// @name Authorization func main() { versionFlag := flag.Bool("version", false, "Print version information") flag.Parse() diff --git a/go.mod b/go.mod index 76768531..e29330d0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gomodel -go 1.24.0 +go 1.25.0 require ( github.com/andybalholm/brotli v1.2.0 @@ -12,6 +12,8 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/redis/go-redis/v9 v9.18.0 github.com/stretchr/testify v1.11.1 + github.com/swaggo/echo-swagger v1.4.1 + github.com/swaggo/swag v1.16.6 github.com/testcontainers/testcontainers-go v0.40.0 github.com/testcontainers/testcontainers-go/modules/mongodb v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 @@ -24,6 +26,7 @@ require ( require ( dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -42,18 +45,25 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -77,6 +87,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/shirou/gopsutil/v4 v4.25.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -94,14 +105,17 @@ require ( go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.46.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.40.0 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index fb01b688..b3f2332f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= @@ -28,6 +30,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -49,6 +52,8 @@ github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0o github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -56,6 +61,19 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -77,12 +95,18 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -99,6 +123,11 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -127,6 +156,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -156,12 +186,24 @@ github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/Vcog github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/testcontainers/testcontainers-go/modules/mongodb v0.40.0 h1:z/1qHeliTLDKNaJ7uOHOx1FjwghbcbYfga4dTFkF0hU= @@ -219,18 +261,18 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= @@ -257,15 +299,15 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= @@ -278,9 +320,15 @@ google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= diff --git a/internal/admin/handler.go b/internal/admin/handler.go index f33d7192..e17ed58d 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -113,6 +113,17 @@ func handleError(c echo.Context, err error) error { } // UsageSummary handles GET /admin/api/v1/usage/summary +// +// @Summary Get usage summary +// @Tags admin +// @Produce json +// @Security BearerAuth +// @Param days query int false "Number of days (default 30)" +// @Param start_date query string false "Start date (YYYY-MM-DD)" +// @Param end_date query string false "End date (YYYY-MM-DD)" +// @Success 200 {object} usage.UsageSummary +// @Failure 400 {object} core.GatewayError +// @Router /admin/api/v1/usage/summary [get] func (h *Handler) UsageSummary(c echo.Context) error { if h.usageReader == nil { return c.JSON(http.StatusOK, usage.UsageSummary{}) @@ -132,6 +143,18 @@ func (h *Handler) UsageSummary(c echo.Context) error { } // DailyUsage handles GET /admin/api/v1/usage/daily +// +// @Summary Get usage breakdown by period +// @Tags admin +// @Produce json +// @Security BearerAuth +// @Param days query int false "Number of days (default 30)" +// @Param start_date query string false "Start date (YYYY-MM-DD)" +// @Param end_date query string false "End date (YYYY-MM-DD)" +// @Param interval query string false "Grouping interval: daily, weekly, monthly, yearly (default daily)" +// @Success 200 {array} usage.DailyUsage +// @Failure 400 {object} core.GatewayError +// @Router /admin/api/v1/usage/daily [get] func (h *Handler) DailyUsage(c echo.Context) error { if h.usageReader == nil { return c.JSON(http.StatusOK, []usage.DailyUsage{}) @@ -155,6 +178,13 @@ func (h *Handler) DailyUsage(c echo.Context) error { } // ListModels handles GET /admin/api/v1/models +// +// @Summary List all registered models with provider info +// @Tags admin +// @Produce json +// @Security BearerAuth +// @Success 200 {array} providers.ModelWithProvider +// @Router /admin/api/v1/models [get] func (h *Handler) ListModels(c echo.Context) error { if h.registry == nil { return c.JSON(http.StatusOK, []providers.ModelWithProvider{}) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index ac1f8551..0f628353 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -79,6 +79,19 @@ func (h *Handler) handleStreamingResponse(c echo.Context, model, provider string } // ChatCompletion handles POST /v1/chat/completions +// +// @Summary Create a chat completion +// @Tags chat +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body core.ChatRequest true "Chat completion request" +// @Success 200 {object} core.ChatResponse +// @Failure 400 {object} core.GatewayError +// @Failure 401 {object} core.GatewayError +// @Failure 429 {object} core.GatewayError +// @Failure 502 {object} core.GatewayError +// @Router /v1/chat/completions [post] func (h *Handler) ChatCompletion(c echo.Context) error { var req core.ChatRequest if err := c.Bind(&req); err != nil { @@ -129,11 +142,26 @@ func (h *Handler) ChatCompletion(c echo.Context) error { } // Health handles GET /health +// +// @Summary Health check +// @Tags system +// @Produce json +// @Success 200 {object} map[string]string +// @Router /health [get] func (h *Handler) Health(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"status": "ok"}) } // ListModels handles GET /v1/models +// +// @Summary List available models +// @Tags models +// @Produce json +// @Security BearerAuth +// @Success 200 {object} core.ModelsResponse +// @Failure 401 {object} core.GatewayError +// @Failure 502 {object} core.GatewayError +// @Router /v1/models [get] func (h *Handler) ListModels(c echo.Context) error { // Create context with request ID for provider requestID := c.Request().Header.Get("X-Request-ID") @@ -148,6 +176,19 @@ func (h *Handler) ListModels(c echo.Context) error { } // Responses handles POST /v1/responses +// +// @Summary Create a model response (Responses API) +// @Tags responses +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body core.ResponsesRequest true "Responses API request" +// @Success 200 {object} core.ResponsesResponse +// @Failure 400 {object} core.GatewayError +// @Failure 401 {object} core.GatewayError +// @Failure 429 {object} core.GatewayError +// @Failure 502 {object} core.GatewayError +// @Router /v1/responses [post] func (h *Handler) Responses(c echo.Context) error { var req core.ResponsesRequest if err := c.Bind(&req); err != nil { diff --git a/internal/server/http.go b/internal/server/http.go index ace9c7a1..18f7da68 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -11,11 +11,14 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" + _ "gomodel/cmd/gomodel/docs" // generated swagger docs "gomodel/internal/admin" "gomodel/internal/admin/dashboard" "gomodel/internal/auditlog" "gomodel/internal/core" "gomodel/internal/usage" + + echoswagger "github.com/swaggo/echo-swagger" ) // Server wraps the Echo server @@ -44,6 +47,8 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { e := echo.New() e.HideBanner = true + e.GET("/swagger/*", echoswagger.WrapHandler) + // Get loggers from config (may be nil) var auditLogger auditlog.LoggerInterface var usageLogger usage.LoggerInterface From 02b71c17d796a08927b2ccbc9a1eed99c7be0afa Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Sun, 22 Feb 2026 18:34:13 +0100 Subject: [PATCH 02/15] chore(gom-25): upgrade to go 1.25 --- .github/workflows/test.yml | 2 +- CLAUDE.md | 2 +- Dockerfile | 2 +- docs/TESTING_STRATEGY.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70562669..bf424591 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.24" + GO_VERSION: "1.25" # Restrict token permissions to minimum required permissions: diff --git a/CLAUDE.md b/CLAUDE.md index 448b53d4..ec369cb4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ Guidance for AI models (like Claude) working with this codebase. **GOModel** is a high-performance AI gateway in Go that routes requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in LiteLLM replacement. -- **Module:** `gomodel` | **Go:** 1.24.0 | **Repo:** https://github.com/ENTERPILOT/GOModel +- **Module:** `gomodel` | **Go:** 1.25.0 | **Repo:** https://github.com/ENTERPILOT/GOModel - **Stage:** Development—backward compatibility is not a concern ## Commands diff --git a/Dockerfile b/Dockerfile index 37ec166f..bb1629e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage — run on the build host's native arch for speed, cross-compile for target -FROM --platform=$BUILDPLATFORM golang:1.24-alpine3.23 AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine3.23 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/docs/TESTING_STRATEGY.md b/docs/TESTING_STRATEGY.md index 7d5a7dd2..2da72da8 100644 --- a/docs/TESTING_STRATEGY.md +++ b/docs/TESTING_STRATEGY.md @@ -211,7 +211,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.24" + go-version: "1.25" # Unit + E2E (no external dependencies) - run: make test-all From 21bc6c83c36370c84bcb3a15edc987ab22eaeaa4 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Sun, 22 Feb 2026 18:44:55 +0100 Subject: [PATCH 03/15] chore(gom-25): clean up Remove hardcoded localhost for host --- cmd/gomodel/docs/docs.go | 4 ++-- cmd/gomodel/docs/swagger.json | 4 +++- cmd/gomodel/docs/swagger.yaml | 3 ++- cmd/gomodel/main.go | 5 ++++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go index 7b3c7b08..97e2ee0d 100644 --- a/cmd/gomodel/docs/docs.go +++ b/cmd/gomodel/docs/docs.go @@ -730,9 +730,9 @@ const docTemplate = `{ // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ Version: "1.0", - Host: "localhost:8080", + Host: "", BasePath: "/", - Schemes: []string{}, + Schemes: []string{"http"}, Title: "GOModel API", Description: "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API.", InfoInstanceName: "swagger", diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json index 2ed4574b..21d82441 100644 --- a/cmd/gomodel/docs/swagger.json +++ b/cmd/gomodel/docs/swagger.json @@ -1,4 +1,7 @@ { + "schemes": [ + "http" + ], "swagger": "2.0", "info": { "description": "High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API.", @@ -6,7 +9,6 @@ "contact": {}, "version": "1.0" }, - "host": "localhost:8080", "basePath": "/", "paths": { "/admin/api/v1/models": { diff --git a/cmd/gomodel/docs/swagger.yaml b/cmd/gomodel/docs/swagger.yaml index 82c01d5a..5115ef3c 100644 --- a/cmd/gomodel/docs/swagger.yaml +++ b/cmd/gomodel/docs/swagger.yaml @@ -258,7 +258,6 @@ definitions: total_tokens: type: integer type: object -host: localhost:8080 info: contact: {} description: High-performance AI gateway routing requests to multiple LLM providers @@ -464,6 +463,8 @@ paths: summary: Create a model response (Responses API) tags: - responses +schemes: +- http securityDefinitions: BearerAuth: in: header diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index db5d0e4f..6740b701 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "gomodel/cmd/gomodel/docs" "gomodel/config" "gomodel/internal/app" "gomodel/internal/observability" @@ -31,8 +32,8 @@ import ( // @title GOModel API // @version 1.0 // @description High-performance AI gateway routing requests to multiple LLM providers (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API. -// @host localhost:8080 // @BasePath / +// @schemes http // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization @@ -108,6 +109,8 @@ func main() { }() addr := ":" + result.Config.Server.Port + docs.SwaggerInfo.Host = strings.TrimPrefix(addr, ":") + docs.SwaggerInfo.Schemes = []string{"http"} if err := application.Start(addr); err != nil { slog.Error("application failed", "error", err) os.Exit(1) From 203a3f0ed4b5ae27d77915dc45b7fbfd50550afa Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Sun, 22 Feb 2026 18:48:04 +0100 Subject: [PATCH 04/15] docs(gom-25): add error response --- internal/admin/handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/admin/handler.go b/internal/admin/handler.go index e17ed58d..c587d67e 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -123,6 +123,7 @@ func handleError(c echo.Context, err error) error { // @Param end_date query string false "End date (YYYY-MM-DD)" // @Success 200 {object} usage.UsageSummary // @Failure 400 {object} core.GatewayError +// @Failure 401 {object} core.GatewayError // @Router /admin/api/v1/usage/summary [get] func (h *Handler) UsageSummary(c echo.Context) error { if h.usageReader == nil { @@ -154,6 +155,7 @@ func (h *Handler) UsageSummary(c echo.Context) error { // @Param interval query string false "Grouping interval: daily, weekly, monthly, yearly (default daily)" // @Success 200 {array} usage.DailyUsage // @Failure 400 {object} core.GatewayError +// @Failure 401 {object} core.GatewayError // @Router /admin/api/v1/usage/daily [get] func (h *Handler) DailyUsage(c echo.Context) error { if h.usageReader == nil { @@ -184,6 +186,7 @@ func (h *Handler) DailyUsage(c echo.Context) error { // @Produce json // @Security BearerAuth // @Success 200 {array} providers.ModelWithProvider +// @Failure 401 {object} core.GatewayError // @Router /admin/api/v1/models [get] func (h *Handler) ListModels(c echo.Context) error { if h.registry == nil { From 9046a47e5b691d83a469d7ba73d204d25be1c0c5 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Sun, 22 Feb 2026 18:49:19 +0100 Subject: [PATCH 05/15] refactor(gom-25): move import --- cmd/gomodel/main.go | 2 ++ internal/server/http.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index 6740b701..0587972d 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -27,6 +27,8 @@ import ( "github.com/lmittmann/tint" "golang.org/x/term" + + _ "gomodel/cmd/gomodel/docs" // generated swagger docs ) // @title GOModel API diff --git a/internal/server/http.go b/internal/server/http.go index 18f7da68..5999da7d 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -11,7 +11,6 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" - _ "gomodel/cmd/gomodel/docs" // generated swagger docs "gomodel/internal/admin" "gomodel/internal/admin/dashboard" "gomodel/internal/auditlog" From 3003bdcbe3aa470e3b4ee51389e5f399ce511802 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Mon, 23 Feb 2026 08:28:24 +0100 Subject: [PATCH 06/15] feat(gom-25): add flag --- CLAUDE.md | 1 + Makefile | 9 +- cmd/gomodel/docs/docs.go | 18 ++ cmd/gomodel/docs/swagger.json | 18 ++ cmd/gomodel/docs/swagger.yaml | 473 ---------------------------------- cmd/gomodel/main.go | 4 - config/config.go | 9 +- internal/app/app.go | 5 + internal/server/http.go | 6 +- 9 files changed, 59 insertions(+), 484 deletions(-) delete mode 100644 cmd/gomodel/docs/swagger.yaml diff --git a/CLAUDE.md b/CLAUDE.md index ec369cb4..12458f4f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,7 @@ make lint-fix # Auto-fix lint issues make tidy # go mod tidy make clean # Remove bin/ make record-api # Record API responses for contract tests +make swagger # Regenerate Swagger docs (requires swag CLI) ``` **Single test:** `go test ./internal/providers -v -run TestName` diff --git a/Makefile b/Makefile index 691ef49b..12a51268 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run clean tidy test test-e2e test-integration test-contract test-all lint lint-fix record-api +.PHONY: build run clean tidy test test-e2e test-integration test-contract test-all lint lint-fix record-api swagger # Get version info VERSION ?= $(shell git describe --tags --always --dirty) @@ -54,6 +54,13 @@ record-api: -output=tests/contract/testdata/openai/models.json @echo "Done! Golden files saved to tests/contract/testdata/" +# Generate Swagger docs (requires swag: go install github.com/swaggo/swag/cmd/swag@latest) +swagger: + cd cmd/gomodel && swag init --generalInfo main.go \ + --dir .,../../internal/server,../../internal/admin,../../internal/usage,../../internal/providers,../../internal/core \ + --output docs \ + --outputTypes go,json + # Run linter lint: golangci-lint run ./... diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go index 97e2ee0d..ee317c96 100644 --- a/cmd/gomodel/docs/docs.go +++ b/cmd/gomodel/docs/docs.go @@ -38,6 +38,12 @@ const docTemplate = `{ "$ref": "#/definitions/providers.ModelWithProvider" } } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } @@ -97,6 +103,12 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/core.GatewayError" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } @@ -147,6 +159,12 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/core.GatewayError" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json index 21d82441..4a7a6d35 100644 --- a/cmd/gomodel/docs/swagger.json +++ b/cmd/gomodel/docs/swagger.json @@ -34,6 +34,12 @@ "$ref": "#/definitions/providers.ModelWithProvider" } } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } @@ -93,6 +99,12 @@ "schema": { "$ref": "#/definitions/core.GatewayError" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } @@ -143,6 +155,12 @@ "schema": { "$ref": "#/definitions/core.GatewayError" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/core.GatewayError" + } } } } diff --git a/cmd/gomodel/docs/swagger.yaml b/cmd/gomodel/docs/swagger.yaml deleted file mode 100644 index 5115ef3c..00000000 --- a/cmd/gomodel/docs/swagger.yaml +++ /dev/null @@ -1,473 +0,0 @@ -basePath: / -definitions: - core.ChatRequest: - properties: - max_tokens: - type: integer - messages: - items: - $ref: '#/definitions/core.Message' - type: array - model: - type: string - reasoning: - $ref: '#/definitions/core.Reasoning' - stream: - type: boolean - stream_options: - $ref: '#/definitions/core.StreamOptions' - temperature: - type: number - type: object - core.ChatResponse: - properties: - choices: - items: - $ref: '#/definitions/core.Choice' - type: array - created: - type: integer - id: - type: string - model: - type: string - object: - type: string - provider: - type: string - usage: - $ref: '#/definitions/core.Usage' - type: object - core.Choice: - properties: - finish_reason: - type: string - index: - type: integer - message: - $ref: '#/definitions/core.Message' - type: object - core.ErrorType: - enum: - - provider_error - - rate_limit_error - - invalid_request_error - - authentication_error - - not_found_error - type: string - x-enum-varnames: - - ErrorTypeProvider - - ErrorTypeRateLimit - - ErrorTypeInvalidRequest - - ErrorTypeAuthentication - - ErrorTypeNotFound - core.GatewayError: - properties: - message: - type: string - provider: - type: string - status_code: - type: integer - type: - $ref: '#/definitions/core.ErrorType' - type: object - core.Message: - properties: - content: - type: string - role: - type: string - type: object - core.Model: - properties: - created: - type: integer - id: - type: string - object: - type: string - owned_by: - type: string - type: object - core.ModelsResponse: - properties: - data: - items: - $ref: '#/definitions/core.Model' - type: array - object: - type: string - type: object - core.Reasoning: - properties: - effort: - description: |- - Effort controls how much reasoning effort the model should use. - Valid values are "low", "medium", and "high". - type: string - type: object - core.ResponsesContentItem: - properties: - annotations: - items: - type: string - type: array - text: - type: string - type: - description: '"output_text", etc.' - type: string - type: object - core.ResponsesError: - properties: - code: - type: string - message: - type: string - type: object - core.ResponsesOutputItem: - properties: - content: - items: - $ref: '#/definitions/core.ResponsesContentItem' - type: array - id: - type: string - role: - type: string - status: - type: string - type: - description: '"message", "function_call", etc.' - type: string - type: object - core.ResponsesRequest: - properties: - input: - description: Can be string or []ResponsesInputItem - instructions: - type: string - max_output_tokens: - type: integer - metadata: - additionalProperties: - type: string - type: object - model: - type: string - reasoning: - $ref: '#/definitions/core.Reasoning' - stream: - type: boolean - stream_options: - $ref: '#/definitions/core.StreamOptions' - temperature: - type: number - tools: - items: - additionalProperties: {} - type: object - type: array - type: object - core.ResponsesResponse: - properties: - created_at: - type: integer - error: - $ref: '#/definitions/core.ResponsesError' - id: - type: string - model: - type: string - object: - description: '"response"' - type: string - output: - items: - $ref: '#/definitions/core.ResponsesOutputItem' - type: array - provider: - type: string - status: - description: '"completed", "failed", "in_progress"' - type: string - usage: - $ref: '#/definitions/core.ResponsesUsage' - type: object - core.ResponsesUsage: - properties: - input_tokens: - type: integer - output_tokens: - type: integer - raw_usage: - additionalProperties: {} - type: object - total_tokens: - type: integer - type: object - core.StreamOptions: - properties: - include_usage: - description: |- - IncludeUsage requests token usage information in streaming responses. - When true, the final streaming chunk will include usage statistics. - type: boolean - type: object - core.Usage: - properties: - completion_tokens: - type: integer - prompt_tokens: - type: integer - raw_usage: - additionalProperties: {} - type: object - total_tokens: - type: integer - type: object - providers.ModelWithProvider: - properties: - model: - $ref: '#/definitions/core.Model' - provider_type: - type: string - type: object - usage.DailyUsage: - properties: - date: - type: string - input_tokens: - type: integer - output_tokens: - type: integer - requests: - type: integer - total_tokens: - type: integer - type: object - usage.UsageSummary: - properties: - total_input_tokens: - type: integer - total_output_tokens: - type: integer - total_requests: - type: integer - total_tokens: - type: integer - type: object -info: - contact: {} - description: High-performance AI gateway routing requests to multiple LLM providers - (OpenAI, Anthropic, Gemini, Groq, xAI, Ollama). Drop-in OpenAI-compatible API. - title: GOModel API - version: "1.0" -paths: - /admin/api/v1/models: - get: - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/providers.ModelWithProvider' - type: array - security: - - BearerAuth: [] - summary: List all registered models with provider info - tags: - - admin - /admin/api/v1/usage/daily: - get: - parameters: - - description: Number of days (default 30) - in: query - name: days - type: integer - - description: Start date (YYYY-MM-DD) - in: query - name: start_date - type: string - - description: End date (YYYY-MM-DD) - in: query - name: end_date - type: string - - description: 'Grouping interval: daily, weekly, monthly, yearly (default daily)' - in: query - name: interval - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/usage.DailyUsage' - type: array - "400": - description: Bad Request - schema: - $ref: '#/definitions/core.GatewayError' - security: - - BearerAuth: [] - summary: Get usage breakdown by period - tags: - - admin - /admin/api/v1/usage/summary: - get: - parameters: - - description: Number of days (default 30) - in: query - name: days - type: integer - - description: Start date (YYYY-MM-DD) - in: query - name: start_date - type: string - - description: End date (YYYY-MM-DD) - in: query - name: end_date - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/usage.UsageSummary' - "400": - description: Bad Request - schema: - $ref: '#/definitions/core.GatewayError' - security: - - BearerAuth: [] - summary: Get usage summary - tags: - - admin - /health: - get: - produces: - - application/json - responses: - "200": - description: OK - schema: - additionalProperties: - type: string - type: object - summary: Health check - tags: - - system - /v1/chat/completions: - post: - consumes: - - application/json - parameters: - - description: Chat completion request - in: body - name: request - required: true - schema: - $ref: '#/definitions/core.ChatRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/core.ChatResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/core.GatewayError' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/core.GatewayError' - "429": - description: Too Many Requests - schema: - $ref: '#/definitions/core.GatewayError' - "502": - description: Bad Gateway - schema: - $ref: '#/definitions/core.GatewayError' - security: - - BearerAuth: [] - summary: Create a chat completion - tags: - - chat - /v1/models: - get: - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/core.ModelsResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/core.GatewayError' - "502": - description: Bad Gateway - schema: - $ref: '#/definitions/core.GatewayError' - security: - - BearerAuth: [] - summary: List available models - tags: - - models - /v1/responses: - post: - consumes: - - application/json - parameters: - - description: Responses API request - in: body - name: request - required: true - schema: - $ref: '#/definitions/core.ResponsesRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/core.ResponsesResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/core.GatewayError' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/core.GatewayError' - "429": - description: Too Many Requests - schema: - $ref: '#/definitions/core.GatewayError' - "502": - description: Bad Gateway - schema: - $ref: '#/definitions/core.GatewayError' - security: - - BearerAuth: [] - summary: Create a model response (Responses API) - tags: - - responses -schemes: -- http -securityDefinitions: - BearerAuth: - in: header - name: Authorization - type: apiKey -swagger: "2.0" diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index 0587972d..fc2a43a8 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -27,8 +27,6 @@ import ( "github.com/lmittmann/tint" "golang.org/x/term" - - _ "gomodel/cmd/gomodel/docs" // generated swagger docs ) // @title GOModel API @@ -111,8 +109,6 @@ func main() { }() addr := ":" + result.Config.Server.Port - docs.SwaggerInfo.Host = strings.TrimPrefix(addr, ":") - docs.SwaggerInfo.Schemes = []string{"http"} if err := application.Start(addr); err != nil { slog.Error("application failed", "error", err) os.Exit(1) diff --git a/config/config.go b/config/config.go index a554c68c..46bc5704 100644 --- a/config/config.go +++ b/config/config.go @@ -276,9 +276,10 @@ type RedisConfig struct { // ServerConfig holds HTTP server configuration type ServerConfig struct { - Port string `yaml:"port" env:"PORT"` - MasterKey string `yaml:"master_key" env:"GOMODEL_MASTER_KEY"` // Optional: Master key for authentication - BodySizeLimit string `yaml:"body_size_limit" env:"BODY_SIZE_LIMIT"` // Max request body size (e.g., "10M", "1024K") + Port string `yaml:"port" env:"PORT"` + MasterKey string `yaml:"master_key" env:"GOMODEL_MASTER_KEY"` // Optional: Master key for authentication + BodySizeLimit string `yaml:"body_size_limit" env:"BODY_SIZE_LIMIT"` // Max request body size (e.g., "10M", "1024K") + SwaggerEnabled bool `yaml:"swagger_enabled" env:"SWAGGER_ENABLED"` // Whether to expose the Swagger UI at /swagger/index.html } // MetricsConfig holds observability configuration for Prometheus metrics @@ -339,7 +340,7 @@ type ResilienceConfig struct { // buildDefaultConfig returns the single source of truth for all configuration defaults. func buildDefaultConfig() *Config { return &Config{ - Server: ServerConfig{Port: "8080"}, + Server: ServerConfig{Port: "8080", SwaggerEnabled: true}, Cache: CacheConfig{ Type: "local", CacheDir: ".cache", diff --git a/internal/app/app.go b/internal/app/app.go index b70d5e58..b9006bf9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -131,6 +131,7 @@ func New(ctx context.Context, cfg Config) (*App, error) { AuditLogger: auditResult.Logger, UsageLogger: usageResult.Logger, LogOnlyModelInteractions: appCfg.Logging.OnlyModelInteractions, + SwaggerEnabled: appCfg.Server.SwaggerEnabled, } // Initialize admin API and dashboard (behind separate feature flags) @@ -157,6 +158,10 @@ func New(ctx context.Context, cfg Config) (*App, error) { slog.Info("admin API disabled") } + if appCfg.Server.SwaggerEnabled { + slog.Info("swagger UI enabled", "path", "/swagger/index.html") + } + app.server = server.New(provider, serverCfg) return app, nil diff --git a/internal/server/http.go b/internal/server/http.go index 5999da7d..e5a4c1f7 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -39,6 +39,7 @@ type Config struct { AdminUIEnabled bool // Whether admin dashboard UI is enabled AdminHandler *admin.Handler // Admin API handler (nil if disabled) DashboardHandler *dashboard.Handler // Dashboard UI handler (nil if disabled) + SwaggerEnabled bool // Whether to expose the Swagger UI at /swagger/index.html } // New creates a new HTTP server @@ -46,8 +47,6 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { e := echo.New() e.HideBanner = true - e.GET("/swagger/*", echoswagger.WrapHandler) - // Get loggers from config (may be nil) var auditLogger auditlog.LoggerInterface var usageLogger usage.LoggerInterface @@ -139,6 +138,9 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { // Public routes e.GET("/health", handler.Health) + if cfg != nil && cfg.SwaggerEnabled { + e.GET("/swagger/*", echoswagger.WrapHandler) + } if cfg != nil && cfg.MetricsEnabled { e.GET(metricsPath, echo.WrapHandler(promhttp.Handler())) } From 7e07d3f088dfeb64ee1eec4dd009bda3f87d1e93 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Mon, 23 Feb 2026 17:35:30 +0100 Subject: [PATCH 07/15] chore(gom-25): clean up --- cmd/gomodel/docs/docs.go | 3 ++- cmd/gomodel/docs/swagger.json | 3 ++- cmd/gomodel/main.go | 1 - internal/core/responses.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go index ee317c96..7f54f329 100644 --- a/cmd/gomodel/docs/docs.go +++ b/cmd/gomodel/docs/docs.go @@ -567,7 +567,8 @@ const docTemplate = `{ "type": "object", "properties": { "input": { - "description": "Can be string or []ResponsesInputItem" + "description": "Can be string or []ResponsesInputItem", + "type": "object" }, "instructions": { "type": "string" diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json index 4a7a6d35..c2670362 100644 --- a/cmd/gomodel/docs/swagger.json +++ b/cmd/gomodel/docs/swagger.json @@ -563,7 +563,8 @@ "type": "object", "properties": { "input": { - "description": "Can be string or []ResponsesInputItem" + "description": "Can be string or []ResponsesInputItem", + "type": "object" }, "instructions": { "type": "string" diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index fc2a43a8..aefcd2e9 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -12,7 +12,6 @@ import ( "syscall" "time" - "gomodel/cmd/gomodel/docs" "gomodel/config" "gomodel/internal/app" "gomodel/internal/observability" diff --git a/internal/core/responses.go b/internal/core/responses.go index 3e48ea78..61783726 100644 --- a/internal/core/responses.go +++ b/internal/core/responses.go @@ -4,7 +4,7 @@ package core // This is the OpenAI-compatible /v1/responses endpoint. type ResponsesRequest struct { Model string `json:"model"` - Input interface{} `json:"input"` // Can be string or []ResponsesInputItem + Input interface{} `json:"input" swaggertype:"object"` // Can be string or []ResponsesInputItem Instructions string `json:"instructions,omitempty"` Tools []map[string]any `json:"tools,omitempty"` Temperature *float64 `json:"temperature,omitempty"` From 7dfe5d43572a6b5d684ad26d7433f6a513dba15a Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 12:32:55 +0100 Subject: [PATCH 08/15] chore(gom-25): simplify command --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 12a51268..e6b9d053 100644 --- a/Makefile +++ b/Makefile @@ -56,9 +56,9 @@ record-api: # Generate Swagger docs (requires swag: go install github.com/swaggo/swag/cmd/swag@latest) swagger: - cd cmd/gomodel && swag init --generalInfo main.go \ - --dir .,../../internal/server,../../internal/admin,../../internal/usage,../../internal/providers,../../internal/core \ - --output docs \ + swag init --generalInfo main.go \ + --dir cmd/gomodel,internal \ + --output cmd/gomodel/docs \ --outputTypes go,json # Run linter From 4c508d34a727fe4ccb8f3ac85a5252bdb2eb4b8b Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 12:33:05 +0100 Subject: [PATCH 09/15] chore(gom-25): exclude swagger from auth --- internal/server/http.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/server/http.go b/internal/server/http.go index e5a4c1f7..876ebbd0 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -139,6 +139,7 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { // Public routes e.GET("/health", handler.Health) if cfg != nil && cfg.SwaggerEnabled { + authSkipPaths = append(authSkipPaths, "/swagger/*") e.GET("/swagger/*", echoswagger.WrapHandler) } if cfg != nil && cfg.MetricsEnabled { From 54691194f761e3a56edbfa753f5926a3bc7f9e9d Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:05:34 +0100 Subject: [PATCH 10/15] chore(gom-25): avoid global install for swagger --- CLAUDE.md | 2 +- Makefile | 3 +-- go.mod | 5 +++++ go.sum | 13 +++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 12458f4f..ef3650c3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ make lint-fix # Auto-fix lint issues make tidy # go mod tidy make clean # Remove bin/ make record-api # Record API responses for contract tests -make swagger # Regenerate Swagger docs (requires swag CLI) +make swagger # Regenerate Swagger docs ``` **Single test:** `go test ./internal/providers -v -run TestName` diff --git a/Makefile b/Makefile index e6b9d053..e8776a6d 100644 --- a/Makefile +++ b/Makefile @@ -54,9 +54,8 @@ record-api: -output=tests/contract/testdata/openai/models.json @echo "Done! Golden files saved to tests/contract/testdata/" -# Generate Swagger docs (requires swag: go install github.com/swaggo/swag/cmd/swag@latest) swagger: - swag init --generalInfo main.go \ + go run github.com/swaggo/swag/cmd/swag init --generalInfo main.go \ --dir cmd/gomodel,internal \ --output cmd/gomodel/docs \ --outputTypes go,json diff --git a/go.mod b/go.mod index e29330d0..66880364 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect @@ -85,11 +86,14 @@ require ( github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -119,4 +123,5 @@ require ( modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index b3f2332f..5d4823ea 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -30,6 +31,8 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -181,8 +184,13 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -214,6 +222,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -325,6 +335,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -361,3 +372,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 3a2bf316a8c1ba39d2bc3474e499d723a648df2b Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:09:15 +0100 Subject: [PATCH 11/15] test(gom-25): add coverage --- internal/server/http_test.go | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/internal/server/http_test.go b/internal/server/http_test.go index 5a7142ea..1eb6826e 100644 --- a/internal/server/http_test.go +++ b/internal/server/http_test.go @@ -8,6 +8,8 @@ import ( "gomodel/internal/admin" "gomodel/internal/admin/dashboard" + + _ "gomodel/cmd/gomodel/docs" ) func TestMetricsEndpoint(t *testing.T) { @@ -399,3 +401,76 @@ func TestHealthEndpointAlwaysAvailable(t *testing.T) { }) } } + +func TestSwaggerEndpoint_Enabled(t *testing.T) { + mock := &mockProvider{} + srv := New(mock, &Config{SwaggerEnabled: true}) + + req := httptest.NewRequest(http.MethodGet, "/swagger/index.html", nil) + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", rec.Code) + } + + contentType := rec.Header().Get("Content-Type") + if !strings.Contains(contentType, "text/html") { + t.Errorf("expected text/html Content-Type, got %s", contentType) + } + + if !strings.Contains(rec.Body.String(), "swagger") { + t.Errorf("expected body to contain swagger UI content, got: %s", rec.Body.String()[:min(200, len(rec.Body.String()))]) + } +} + +func TestSwaggerEndpoint_Disabled(t *testing.T) { + mock := &mockProvider{} + srv := New(mock, &Config{SwaggerEnabled: false}) + + req := httptest.NewRequest(http.MethodGet, "/swagger/index.html", nil) + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Errorf("expected status 404, got %d", rec.Code) + } +} + +func TestSwaggerEndpoint_NilConfig(t *testing.T) { + mock := &mockProvider{} + srv := New(mock, nil) + + req := httptest.NewRequest(http.MethodGet, "/swagger/index.html", nil) + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Errorf("expected status 404, got %d", rec.Code) + } +} + +func TestSwaggerDocJson_ReturnsExpectedContent(t *testing.T) { + mock := &mockProvider{} + srv := New(mock, &Config{SwaggerEnabled: true}) + + req := httptest.NewRequest(http.MethodGet, "/swagger/doc.json", nil) + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", rec.Code) + } + + body := rec.Body.String() + if !strings.Contains(body, "GOModel") { + t.Errorf("expected doc.json to contain GOModel API title, got: %s", body[:min(300, len(body))]) + } + if !strings.Contains(body, "swagger") { + t.Errorf("expected doc.json to contain swagger spec, got: %s", body[:min(300, len(body))]) + } +} From 09fe4fb291454f25679bd3304cb44057e11411be Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:11:23 +0100 Subject: [PATCH 12/15] fix(gom-25): init swagger docs --- cmd/gomodel/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index aefcd2e9..cbbf536d 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + _ "gomodel/cmd/gomodel/docs" "gomodel/config" "gomodel/internal/app" "gomodel/internal/observability" From 28107f94b36d99cd2c317ef8a5afb84033eded4a Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:11:58 +0100 Subject: [PATCH 13/15] Update internal/server/handlers.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/server/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 0f628353..aa36370e 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -83,10 +83,10 @@ func (h *Handler) handleStreamingResponse(c echo.Context, model, provider string // @Summary Create a chat completion // @Tags chat // @Accept json -// @Produce json +// @Produce json text/event-stream // @Security BearerAuth // @Param request body core.ChatRequest true "Chat completion request" -// @Success 200 {object} core.ChatResponse +// @Success 200 {object} core.ChatResponse "JSON response or SSE stream when stream=true" // @Failure 400 {object} core.GatewayError // @Failure 401 {object} core.GatewayError // @Failure 429 {object} core.GatewayError From daab2b03428b4e2a7217306ee7bc0a542522f0c7 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:38:29 +0100 Subject: [PATCH 14/15] chore(gom-25): remove type constraint --- cmd/gomodel/docs/docs.go | 5 +++-- cmd/gomodel/docs/swagger.json | 5 +++-- internal/core/responses.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/gomodel/docs/docs.go b/cmd/gomodel/docs/docs.go index 334be342..c6086f12 100644 --- a/cmd/gomodel/docs/docs.go +++ b/cmd/gomodel/docs/docs.go @@ -567,8 +567,9 @@ const docTemplate = `{ "type": "object", "properties": { "input": { - "description": "Can be string or []ResponsesInputItem", - "type": "object" + "description": "string or []ResponsesInputItem — see docs for array form", + "type": "string", + "example": "Tell me a joke" }, "instructions": { "type": "string" diff --git a/cmd/gomodel/docs/swagger.json b/cmd/gomodel/docs/swagger.json index d8afa420..42b16c14 100644 --- a/cmd/gomodel/docs/swagger.json +++ b/cmd/gomodel/docs/swagger.json @@ -563,8 +563,9 @@ "type": "object", "properties": { "input": { - "description": "Can be string or []ResponsesInputItem", - "type": "object" + "description": "string or []ResponsesInputItem — see docs for array form", + "type": "string", + "example": "Tell me a joke" }, "instructions": { "type": "string" diff --git a/internal/core/responses.go b/internal/core/responses.go index 61783726..9babee96 100644 --- a/internal/core/responses.go +++ b/internal/core/responses.go @@ -4,7 +4,7 @@ package core // This is the OpenAI-compatible /v1/responses endpoint. type ResponsesRequest struct { Model string `json:"model"` - Input interface{} `json:"input" swaggertype:"object"` // Can be string or []ResponsesInputItem + Input interface{} `json:"input" swaggertype:"string" example:"Tell me a joke"` // string or []ResponsesInputItem — see docs for array form Instructions string `json:"instructions,omitempty"` Tools []map[string]any `json:"tools,omitempty"` Temperature *float64 `json:"temperature,omitempty"` From 15c952472d33137799a5bbe35df82ac1fe230283 Mon Sep 17 00:00:00 2001 From: "giorgi.pro" Date: Tue, 24 Feb 2026 13:41:04 +0100 Subject: [PATCH 15/15] fix(gom-25): lint --- internal/server/http.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/server/http.go b/internal/server/http.go index 876ebbd0..3ad2a9f9 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -81,6 +81,9 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { if cfg != nil && cfg.AdminUIEnabled && cfg.DashboardHandler != nil { authSkipPaths = append(authSkipPaths, "/admin/dashboard", "/admin/dashboard/*", "/admin/static/*") } + if cfg != nil && cfg.SwaggerEnabled { + authSkipPaths = append(authSkipPaths, "/swagger/*") + } // Global middleware stack (order matters) // Request logger with optional filtering for model-only interactions @@ -139,7 +142,6 @@ func New(provider core.RoutableProvider, cfg *Config) *Server { // Public routes e.GET("/health", handler.Health) if cfg != nil && cfg.SwaggerEnabled { - authSkipPaths = append(authSkipPaths, "/swagger/*") e.GET("/swagger/*", echoswagger.WrapHandler) } if cfg != nil && cfg.MetricsEnabled {