Files
llmgw/internal/clientapi/routes.go
T
Ilker ManapandClaude Opus 5.5 b6bfc33ef7 Any ComfyUI workflow runs as a job, from the inputs its catalog entry declares
A model's catalog entry now says what its workflow takes and makes:
comfyui.inputs, each a placeholder of its name with a type (text, int,
float, bool, choice, image, mask, audio, video, file), bounds, choices
and a default; and comfyui.output, which gains video, audio and files
beside pictures and meshes. POST /v1/workflows/runs with {model, inputs}
checks a request against them before a job is written — a required input
left out, an unknown one, a number out of bounds, a song sent as a
picture, another owner's file id are each refused with what was wrong —
and the job worker replays it to a replica, where the files go into
ComfyUI's input directory, the workflow is filled and run, and the first
file of the model's output kind is the job's result. /v1/models lists
each ComfyUI model's workflow, so a client can build its form.

A PNG result carries the licence in an iTXt chunk, as a GLB does in
asset.extras. A file whose server named no type is typed by its name
before its bytes, so a WAV is audio/wav, not audio/wave.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 14:28:15 +02:00

124 lines
7.8 KiB
Go

package clientapi
import (
"encoding/json"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/router"
)
type (
// Route and Param are the admin API's, which one generator reads.
Route = adminapi.Route
Param = adminapi.Param
)
// PathParams describes the parameters in Routes' paths.
var PathParams = map[string]string{
"files/{id}": "The file's ID, file-….",
"batches/{id}": "The batch's ID, batch_….",
"generations/{id}": "The job's ID, job_….",
"runs/{id}": "The job's ID, job_….",
"threads/{id}": "The conversation's ID, as sent in x-llmgw-thread.",
"memory/{key}": "The note's key, chosen by the caller.",
"collections/{id}": "The collection's ID, its name when that is unique, or ORG/NAME.",
}
// any is a body whose shape is the engine's or the provider's own.
var anyJSON = json.RawMessage{}
// Routes is every endpoint the client API serves, but /health, /ready and
// /leader, which are a load balancer's and need no key.
var Routes = []Route{
{Method: "POST", Path: "/v1/chat/completions", Summary: "A chat turn",
Description: "A model's name, an alias, or an adapter's name. Fields not listed go to the engine as sent. " +
"With a knowledge base, x-llmgw-knowledge names the collections to answer from; x-llmgw-thread continues a stored conversation.",
Request: ChatCompletionRequest{}, Response: ChatCompletion{}, Stream: true},
{Method: "POST", Path: "/v1/completions", Summary: "A prompt continued",
Request: CompletionRequest{}, Response: Completion{}, Stream: true},
{Method: "POST", Path: "/v1/embeddings", Summary: "Vectors for texts",
Request: EmbeddingRequest{}, Response: EmbeddingList{}},
{Method: "POST", Path: "/v1/rerank", Summary: "Documents ordered by a query, for a reranker vLLM serves",
Request: RerankRequest{}, Response: anyJSON},
{Method: "POST", Path: "/v1/score", Summary: "Pairs of texts scored, for a scorer vLLM serves",
Request: ScoreRequest{}, Response: anyJSON},
{Method: "POST", Path: "/v1/audio/transcriptions", Summary: "Speech to text",
Request: TranscriptionForm{}, Form: true, Response: Transcription{}},
{Method: "POST", Path: "/v1/audio/translations", Summary: "Speech to English text",
Request: TranscriptionForm{}, Form: true, Response: Transcription{}},
{Method: "POST", Path: "/v1/images/generations", Summary: "Pictures, of a model that makes them",
Description: "Made by ComfyUI. Pictures come back as b64_json and are kept nowhere; x-llmgw-seed says the seed.",
Request: ImageRequest{}, Response: ImagesResponse{}},
{Method: "POST", Path: "/v1/3d/generations", Summary: "A 3D model from a picture, as a job",
Description: "Answered 202 with the job; ask after it until it is completed, then download output_file_id from /v1/files.",
Request: MeshRequest{}, Response: Job{}, Status: 202},
{Method: "GET", Path: "/v1/3d/generations", Summary: "The caller's 3D jobs, newest first",
Query: []Param{
{Name: "limit", Type: "integer", Description: "The most jobs to answer with, at most 100; 20 when absent."},
{Name: "after", Description: "A job's ID: those asked for before it."},
},
Response: JobList{}},
{Method: "GET", Path: "/v1/3d/generations/{id}", Summary: "A 3D job: its status, progress and result", Response: Job{}},
{Method: "POST", Path: "/v1/3d/generations/{id}/cancel", Summary: "Stop a 3D job, in the engine's queue too", Response: Job{}},
{Method: "POST", Path: "/v1/workflows/runs", Summary: "Run a ComfyUI model's workflow, as a job",
Description: "Inputs are those the model's workflow lists in /v1/models. Answered 202 with the job; " +
"download output_file_id from /v1/files once it is completed.",
Request: RunRequest{}, Response: Job{}, Status: 202},
{Method: "GET", Path: "/v1/workflows/runs", Summary: "The caller's jobs, newest first",
Query: []Param{
{Name: "limit", Type: "integer", Description: "The most jobs to answer with, at most 100; 20 when absent."},
{Name: "after", Description: "A job's ID: those asked for before it."},
},
Response: JobList{}},
{Method: "GET", Path: "/v1/workflows/runs/{id}", Summary: "A run: its status, progress and result", Response: Job{}},
{Method: "POST", Path: "/v1/workflows/runs/{id}/cancel", Summary: "Stop a run, in the engine's queue too", Response: Job{}},
{Method: "POST", Path: "/v1/moderations", Summary: "Texts judged by the guardrails' judge model",
Request: ModerationRequest{}, Response: ModerationResponse{}},
{Method: "POST", Path: "/v1/responses", Summary: "OpenAI's Responses API, for Codex and the like",
Request: ResponsesRequest{}, Response: anyJSON, Stream: true},
{Method: "POST", Path: "/v1/messages", Summary: "Anthropic's Messages API, for Claude Code and Anthropic's SDKs",
Description: "The key is sent as x-api-key or as a bearer token.",
Request: MessagesRequest{}, Response: anyJSON, Stream: true},
{Method: "POST", Path: "/v1/messages/count_tokens", Summary: "How many tokens a Messages request would be",
Request: MessagesRequest{}, Response: TokenCount{}},
{Method: "GET", Path: "/v1/models", Summary: "The models the key may use", Response: ModelList{}},
{Method: "POST", Path: "/v1/files", Summary: "Upload a file: a batch's input",
Request: FileForm{}, Form: true, Response: File{}},
{Method: "GET", Path: "/v1/files", Summary: "The caller's files",
Query: []Param{{Name: "purpose", Description: "Only files of this purpose: batch, batch_output, job_output."}},
Response: FileList{}},
{Method: "GET", Path: "/v1/files/{id}", Summary: "A file", Response: File{}},
{Method: "GET", Path: "/v1/files/{id}/content", Summary: "A file's content",
Binary: "application/octet-stream"},
{Method: "DELETE", Path: "/v1/files/{id}", Summary: "Delete a file", Response: Deleted{}},
{Method: "POST", Path: "/v1/batches", Summary: "Run a file of requests at the lowest priority",
Request: BatchRequest{}, Response: Batch{}},
{Method: "GET", Path: "/v1/batches", Summary: "The caller's batches",
Query: []Param{
{Name: "limit", Type: "integer", Description: "The most batches to answer with; 20 when absent."},
{Name: "after", Description: "A batch's ID: those made before it."},
},
Response: BatchList{}},
{Method: "GET", Path: "/v1/batches/{id}", Summary: "A batch and how far it is", Response: Batch{}},
{Method: "POST", Path: "/v1/batches/{id}/cancel", Summary: "Stop a batch", Response: Batch{}},
{Method: "DELETE", Path: "/v1/threads/{id}", Summary: "Forget a stored conversation"},
{Method: "PUT", Path: "/v1/memory/{key}", Summary: "Keep a note for the project",
Request: MemoryWrite{}, Response: MemoryNote{}},
{Method: "GET", Path: "/v1/memory/{key}", Summary: "A note", Response: MemoryNote{}},
{Method: "GET", Path: "/v1/memory", Summary: "The project's notes", Response: MemoryList{}},
{Method: "DELETE", Path: "/v1/memory/{key}", Summary: "Forget a note"},
{Method: "POST", Path: "/mcp", Summary: "The gateway's MCP server: the memory notes as tools",
Request: MCPRequest{}, Response: anyJSON},
{Method: "GET", Path: "/me", Summary: "The caller: its key, its place in the tree, its limits and what it has used",
Response: Me{}},
{Method: "GET", Path: "/v1/kb/collections", Summary: "The knowledge-base collections the caller may read",
Response: []router.KBCollectionView{}},
{Method: "GET", Path: "/v1/kb/collections/{id}/manifest", Summary: "A collection and its documents",
Response: router.KBManifest{}},
{Method: "PUT", Path: "/v1/kb/collections/{id}/documents", Summary: "Add or replace a document, read and embedded by the client",
Request: router.KBPutDocument{}, Response: router.KBPutResult{}},
{Method: "DELETE", Path: "/v1/kb/collections/{id}/documents", Summary: "Take a document out of a collection",
Query: []Param{{Name: "name", Required: true, Description: "The document's name, as the manifest lists it."}},
Response: KBDeleted{}},
}