meridian

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

README

Gauss Meridian

meridian-go

The official Go SDK for Gauss Meridian — one API for many AI models, routed by Meridian.

Status: Public preview, v0.1.0 (meridian.Version). Needs Go 1.23 or newer. No dependencies beyond the Go standard library.

  • A server client for chat (buffered and streamed) and the model list.
  • A forwarding handler (forward) that lets a browser or mobile app chat through your own route, with your key kept on your server.
  • An admin client for integrators: organisations, projects, keys, provider credentials, model allowlists, usage and logs.
  • One error type, *meridian.Error, that you match with errors.As.

Contents

  1. Install
  2. Quick start
  3. Three ways to connect
  4. Response contract
  5. Streaming
  6. Forwarding route
  7. Admin client
  8. Retries, timeouts and idempotency
  9. Configuration
  10. Contract run (simulator only)
  11. Development

Install

go get github.com/Gaussian-id/meridian-go@v0.1.0
import "github.com/Gaussian-id/meridian-go" // package meridian

Quick start

Keys spend credit. A project key (gmr_…) and an integrator key (gmi_…) belong on your server, never in a browser or an app. Read them from the environment; no example here contains a key.

export MERIDIAN_URL=http://127.0.0.1:8180     # the public listener (the admin listener is 8181)
export MERIDIAN_KEY=...                       # your project key, from your secret store

A buffered chat call:

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/Gaussian-id/meridian-go"
)

func main() {
	client := meridian.NewClient(os.Getenv("MERIDIAN_URL"), os.Getenv("MERIDIAN_KEY"))

	reply, err := client.Chat.Create(context.Background(), meridian.ChatParams{
		Messages: []meridian.ChatMessage{{Role: meridian.RoleUser, Content: "Halo"}},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(reply.Text())
}

A streamed call:

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/Gaussian-id/meridian-go"
)

func main() {
	client := meridian.NewClient(os.Getenv("MERIDIAN_URL"), os.Getenv("MERIDIAN_KEY"))

	stream, err := client.Chat.Stream(context.Background(), meridian.ChatParams{
		Messages: []meridian.ChatMessage{{Role: meridian.RoleUser, Content: "Halo"}},
	})
	if err != nil {
		log.Fatal(err)
	}

	for chunk, err := range stream.Chunks() {
		if err != nil {
			log.Fatal(err) // a failure arrives as the error of the last iteration
		}

		fmt.Print(chunk.Text())
	}

	if u := stream.Usage(); u != nil && u.TotalTokens != nil {
		fmt.Println("\ntotal tokens:", *u.TotalTokens)
	}
}
  • NewClient never fails. A bad URL or an empty key gives a client whose every call returns an invalid_request error and sends nothing, so a missing environment variable shows up at the first call, not as a crash at start-up.
  • Leave ChatParams.Model empty to let Meridian choose; the SDK never fills a model in. Fields the struct does not have go in ChatParams.Extra.
  • Chat.Create always sends "stream": false; Chat.Stream always sends "stream": true and asks for usage ("stream_options": {"include_usage": true}), whatever Extra holds.
  • client.Models.List(ctx) returns the models the key may use.
  • Pass the incoming request's context so that a browser disconnect cancels the engine call.

Three ways to connect

Way Use it when Key lives
Server client Your Go code calls Meridian Your server
Forwarding route A browser or mobile app chats through your route Your server
Direct HTTP API Another language, or a quick test Wherever the caller runs
1. The server client

meridian.NewClient(baseURL, key, opts...) returns a *meridian.Client with Chat (Create, Stream) and Models (List). It is safe for concurrent use. The examples under Quick start and Streaming use it. To relay a stream from your own route, see ServeSSE under Streaming.

2. Browsers and mobile apps: the forward handler

A browser must not hold your key. forward.New returns an http.Handler that you mount on your own route; it forwards a browser's chat to Meridian with your server's key.

package main

import (
	"log"
	"net/http"
	"os"

	"github.com/Gaussian-id/meridian-go"
	"github.com/Gaussian-id/meridian-go/forward"
)

func main() {
	chat, err := forward.New(forward.Options{
		// Built for every request, so a missing variable cannot stop the process from starting.
		Client: func(r *http.Request) (*meridian.Client, error) {
			return meridian.NewClient(os.Getenv("MERIDIAN_URL"), os.Getenv("MERIDIAN_KEY")), nil
		},
		// Required. Decide who may spend your credit, for example from your own session.
		Authorize: func(w http.ResponseWriter, r *http.Request) (forward.Decision, error) {
			if signedIn(r) {
				return forward.Allow, nil
			}

			return forward.Deny, nil // the browser gets 403 permission_denied
		},
	})
	if err != nil { // only when Authorize or Client is missing
		log.Fatal(err)
	}

	mux := http.NewServeMux()
	mux.Handle("/api/chat", chat) // the handler answers non-POST requests with 405 itself

	log.Fatal(http.ListenAndServe(":8080", mux))
}

func signedIn(r *http.Request) bool { _, err := r.Cookie("session"); return err == nil }

It mounts on any router that takes an http.Handler:

Router Mount
net/http mux.Handle("/api/chat", chat)
chi r.Handle("/api/chat", chat)
Gin r.Any("/api/chat", gin.WrapH(chat))
Echo e.Any("/api/chat", echo.WrapHandler(chat))

The chi, Gin and Echo lines are not compiled here: the SDK has no dependency on them, and this README's programs are checked against the standard library only. What the handler accepts and what a browser can learn from a failure is under Forwarding route.

3. The direct API

The engine speaks the OpenAI chat-completions shape, so any HTTP client works:

curl -N "$MERIDIAN_URL/v1/chat/completions" \
  -H "Authorization: Bearer $MERIDIAN_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Halo"}],"stream":true,"stream_options":{"include_usage":true}}'

A stream is a series of data: {…} events that ends with data: [DONE]. A failure after streaming began is one last data: {"error":{…}} event with no [DONE].

Response contract

Every client call returns (value, error). When error is non-nil, the value is nil. The SDK does not panic on a bad argument or a bad reply (a nil client or service, a nil context, a bad key): it returns an error and sends nothing. Every non-nil error from a client call is a *meridian.Error; reach it with errors.As.

The one exception. forward.New returns a plain sentinel error, not a *meridian.Error, when Authorize or Client is missing: forward.ErrNoAuthorize or forward.ErrNoClient. That is a programming error at start-up, so it comes from a constructor, not from a call.

The reply

Chat.Create returns a *meridian.ChatCompletion:

Field Type Meaning
ID, Object, Created, Model string, string, int64, string As the engine sent them
Choices []ChatChoice Index, Message (Role, Content *string, Reasoning, ReasoningContent, Refusal, ToolCalls), FinishReason
Usage Usage Three token counts, below
RequestID string The engine's X-Request-Id; not part of the body. Give it to Gaussian support
Text() method The first choice's content, or ""

A stream yields meridian.ChatChunk values: ID, Object, Created, Model, Choices (Index, Delta with Role, Content, Reasoning, ReasoningContent, Refusal, ToolCalls, and FinishReason *string), and Usage *Usage. chunk.Text() returns the first choice's new content, or "". Chunks that carry only usage are recorded for stream.Usage(), not yielded.

Usage is exactly three optional token counts: PromptTokens, CompletionTokens and TotalTokens (each *int, nil when the engine did not report one, never 0 for unknown). No exported type carries a price or a cost (D-27). If your billing needs supplier rates, it reads them from the engine's admin API directly.

The error
type Error struct {
	Code       string // one of the 28 engine codes, or network_error / aborted
	Type       string // one of the engine's five types, or client_error
	Status     int    // HTTP status of the engine's reply; 0 when none arrived
	Message    string // the engine's message, or the SDK's own explanation
	Retryable  bool   // whether the same call could succeed if tried again
	RequestID  string // the engine's request id, when known
	EngineCode string // the raw code when this SDK does not recognise it; empty otherwise
	// plus an unexported Retry-After hint and cause
}
Field Meaning
Code One of the engine's 28 codes (meridian.EngineCodes()), or network_error / aborted, the only codes the SDK adds. Each has a constant: meridian.CodeRateLimited, meridian.CodeNetworkError, and so on
Type invalid_request_error, authentication_error, permission_error, rate_limit_error or api_error (meridian.EngineTypes()), or client_error for a failure inside the SDK. Constants: meridian.TypeAPI, meridian.TypeClient, …
Message Human-readable. Safe to log; it never contains your key
Status The HTTP status of the engine's reply. A malformed or unreadable reply keeps its real status. It is 0 when no HTTP reply arrived, and for an SDK-side failure inside a stream that had begun (a truncation, an unreadable frame); engine error frames carry the status for their code
RequestID The engine's id when known, also for a failure inside a stream. Only 1–255 printable ASCII bytes are kept; anything else is dropped
Retryable True for rate_limited, upstream_unavailable, service_unavailable, deadline_exceeded and network_error. An explicit "retryable": false from the engine turns it off
EngineCode A code the SDK does not recognise (a newer engine) is decoded as internal, with type api_error, not retried, and the raw code here. Empty otherwise
Retry hint The engine's Retry-After (seconds or an HTTP date). There is no exported field for it. The SDK honours it for you (see Retries), and meridian.WriteError repeats it on a rate_limited answer from your own handler

Error() prints meridian: <code> (HTTP <status>): <message> [request <id>], leaving out the status when it is 0 and the request id when it is empty.

Matching an error

Use errors.As to reach the fields, and compare Code to a constant. There is one *meridian.Error type and no sentinel error per code, so errors.Is(err, someCode) does not exist. errors.Is works for the cause the SDK wraps: a cancelled or expired context.Context gives aborted, and errors.Is(err, context.Canceled) or errors.Is(err, context.DeadlineExceeded) still holds.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/Gaussian-id/meridian-go"
)

func main() {
	client := meridian.NewClient(os.Getenv("MERIDIAN_URL"), os.Getenv("MERIDIAN_KEY"))

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()

	reply, err := client.Chat.Create(ctx, meridian.ChatParams{
		Messages: []meridian.ChatMessage{{Role: meridian.RoleUser, Content: "Halo"}},
	})
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			log.Fatal("took longer than 20 s") // code "aborted"
		}

		var me *meridian.Error
		if !errors.As(err, &me) {
			log.Fatal(err) // not reachable from the SDK; kept for your own wrappers
		}

		switch {
		case me.Code == meridian.CodeInsufficientCredit:
			log.Fatal("top up the project") // your account, not the end user's
		case me.Retryable:
			log.Fatalf("the SDK already retried; try later (%s)", me.Code)
		default:
			// Give the request id to Gaussian support.
			log.Fatalf("%s (%s, HTTP %d) request %s: %s", me.Code, me.Type, me.Status, me.RequestID, me.Message)
		}
	}

	fmt.Println(reply.Text(), reply.RequestID)
}

meridian.WriteError(w, err) writes an error as the engine's JSON envelope from your own handler, with the error's status (502 when it has none). It hides nothing, so a route that browsers call should use forward, and it shows client_error as api_error.

All error codes

The status is the engine's HTTP status for the code. It is also what Status holds for a code that arrives inside a stream. "Retry" is the value of Retryable.

Code HTTP Retry Typical cause
invalid_request 400 no The request is malformed. The SDK uses it, with type client_error, for a bad URL, empty key, bad idempotency key or unusable argument
invalid_api_key 401 no The key is unknown
key_revoked 401 no The key was revoked
key_expired 401 no The key expired
project_unavailable 403 no The key's project cannot be used
organisation_unavailable 403 no The key's organisation cannot be used
model_not_allowed 403 no The project's allowlist excludes the model
byok_required 403 no The organisation must bring its own provider key
project_host_mismatch 403 no The key is bound to another host
model_not_found 404 no No such model
route_not_found 404 no No such route
method_not_allowed 405 no Wrong HTTP method
capability_unsupported 400 no The model cannot do what was asked (for example vision)
body_too_large 413 no The request body is over the engine's limit
rate_limited 429 yes Too many requests; the engine did not run the call
insufficient_credit 402 no The organisation is out of credit
hard_limit_reached 429 no A hard spending limit was reached
entitlement_missing 403 no The organisation lacks the entitlement
idempotency_conflict 409 no The idempotency key is in use (see Idempotency)
upstream_unavailable 502 yes The model provider could not be reached
upstream_error 502 no The model provider answered with an error
deadline_exceeded 504 yes The engine's deadline passed
service_unavailable 503 yes The engine is temporarily unavailable
internal 500 no An engine fault; also what an unrecognised code becomes
invalid_token 401 no An admin token is invalid
permission_denied 403 no Not allowed
not_found 404 no No such resource
conflict 409 no The change conflicts with the current state
network_error whatever arrived, else 0 yes when no reply arrived, or the status is 429 or 5xx SDK-added. No connection, a reset, or a reply that is not a Meridian error body
aborted 0 no SDK-added. Your context was cancelled or its deadline passed

The status and retry columns come from the SDK's own tables (codes.go). The "Typical cause" column is a reading of each code's name: the engine documents in spec/ list the codes but do not describe them, so rely on Message for the exact reason.

Streaming

Chat.Stream returns a *meridian.ChatStream. Read it once, with one of:

Read with Gives you
stream.Chunks() A iter.Seq2[ChatChunk, error] for for chunk, err := range …
stream.Text() The whole reply as a string. On failure it returns the text so far and the error
stream.ServeSSE(w, mapError) A byte-for-byte relay to an http.ResponseWriter, flushing after each event
  • Usage. stream.Usage() returns the token counts reported at the end of the stream, or nil before then (or if the engine reported none). It returns a copy.
  • Request id. stream.RequestID() is the engine's id for the stream, known before the first chunk.
  • Cancel. Cancel the context you passed to Chat.Stream, or break out of the Chunks loop, which closes the stream and cancels the upstream request. A stream you never read must be released with stream.Close() (safe to call more than once).
  • A failure mid-stream arrives as the error of the last iteration of Chunks (or the second return of Text): an engine error frame has the code and status for its code; a dropped connection is network_error; a cancelled context is aborted. Chunks already received stay valid. A stream is never retried once it has begun.
  • Second read. Reading a stream a second time, or after Close, returns an aborted error.
  • Relaying from your own route. ServeSSE returns the error that ended the stream (nil after [DONE]). mapError sees every error that ends the stream and may return a replacement, which is written as the final error frame; returning nil keeps the engine's own frame. With a nil mapError, an SDK-side failure writes nothing more.
package main

import (
	"errors"
	"log"
	"net/http"
	"os"

	"github.com/Gaussian-id/meridian-go"
)

func main() {
	client := meridian.NewClient(os.Getenv("MERIDIAN_URL"), os.Getenv("MERIDIAN_KEY"))

	http.HandleFunc("/relay", func(w http.ResponseWriter, r *http.Request) {
		// Pass r.Context() so a browser disconnect cancels the engine call.
		stream, err := client.Chat.Stream(r.Context(), meridian.ChatParams{
			Messages: []meridian.ChatMessage{{Role: meridian.RoleUser, Content: "Halo"}},
		})
		if err != nil {
			meridian.WriteError(w, err) // shows the real code; fine for a route only you call
			return
		}

		if err := stream.ServeSSE(w, nil); err != nil {
			var me *meridian.Error
			if errors.As(err, &me) {
				log.Printf("stream ended: %s request %s", me.Code, me.RequestID)
			}
		}
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Forwarding route

What forward.New does, in order:

  1. POST only: anything else gets 405 with Allow: POST.

  2. Content-Type: application/json (parameters allowed), else 415. A cross-site form or no-cors fetch cannot send that type without a CORS preflight, so this is the CSRF guard, and it runs before Authorize.

  3. Authorize (required, or New returns forward.ErrNoAuthorize). It returns a forward.Decision:

    Return Effect
    forward.Allow The request continues
    forward.Deny 403 permission_denied, no engine request
    forward.Handled You already wrote your own response (a redirect, a 401); the handler writes nothing more
    an error, an unknown decision, or a panic 403 permission_denied, no engine request

    Authorize runs before the body is read, and the body is capped. If it must read the body, it has to put it back (http.MaxBytesReader, io.ReadAll, then r.Body = io.NopCloser(bytes.NewReader(data))).

  4. The body: JSON only, at most 8 MiB (413 beyond that), valid UTF-8, nested at most 64 levels (400 otherwise).

  5. The allowlist, applied to what the browser sent:

    • messages (a non-empty array of objects) is required and forwarded as sent, so a browser can send a system message of its own. The handler adds no system prompt; if your app needs a fixed one, use your own route with the server client instead.
    • max_tokens is always sent. If the browser omits it, or sends something that is not a number, the cap is sent (forward.DefaultMaxTokensCap, 4096, or AllowRule.MaxTokensCap); otherwise it is clamped to [1, cap]. A cap above math.MaxInt32 is lowered to it.
    • temperature only if it is a JSON number; model only if AllowRule.Model is true; extra fields only by name in AllowRule.Options.
    • Never copied raw, even from AllowRule.Options: messages, model, stream, stream_options, max_tokens, max_completion_tokens, n and temperature. A browser cannot ask for more output than the cap.
    • A browser Idempotency-Key is forwarded when it is 1–255 bytes and every byte is visible ASCII (0x21–0x7E, so no spaces, as the engine requires); otherwise the client generates one.
  6. The engine call uses the request's context, so a browser disconnect cancels it upstream. "stream": true in the body relays the engine's stream (always asking for usage); otherwise the reply is buffered and returned as JSON.

What a browser may learn from a failure
  • The app's own account problems never reach it. The ten codes invalid_api_key, key_revoked, key_expired, project_unavailable, organisation_unavailable, project_host_mismatch, byok_required, insufficient_credit, hard_limit_reached and entitlement_missing become 503, code service_unavailable, message "The service is temporarily unavailable.", retryable: false, with the engine's request id, also when they arrive as a mid-stream error frame. So do codes the SDK does not know (a newer engine could add one about your account), error bodies or frames with no usable code, a 200 reply that carries an error member, and every SDK-side failure: configuration errors, an unreachable engine, a cancelled call, a failing client factory. forward.AppAccountCodes() lists the ten.
  • Stream content the guard cannot judge fails closed. An event type other than message, data that is not a JSON chat chunk or error frame, a stream that ends before [DONE] or without any data: line: the browser gets the generic error frame as the last event, never [DONE], and the upstream request is closed.
  • Everything else passes through unchanged: rate_limited, invalid_request, model_not_found, deadline_exceeded, and the rest of the engine's codes. A mid-stream engine error frame that passes is relayed byte for byte. A rate_limited answer before streaming starts, or for a buffered call, also repeats the engine's Retry-After header when it is a valid number of seconds or an HTTP date; the generic 503 never carries one.
  • The stream guard assumes a direct connection from the SDK to the engine. It reads the engine's own framing (data: events, [DONE]) and fails closed on anything else, so a proxy between them that adds event: lines or other fields makes every stream end in the generic error frame.
  • Request ids in the error bodies and headers that the SDK builds are cleaned: they are put there only when they are 1–255 printable ASCII bytes, and any other value is left out. A mid-stream frame that passes through is the engine's own bytes, so its request id is the engine's, JSON-escaped as the engine wrote it.

Your own server-side calls through meridian.Client still see the real codes.

Admin client

For integrators who provision organisations and projects. It uses an integrator key (gmi_…) on the engine's separate admin address, and, like the project key, belongs on your server only.

package main

import (
	"context"
	"log"
	"os"

	"github.com/Gaussian-id/meridian-go"
)

func main() {
	ctx := context.Background()
	admin := meridian.NewAdminClient(os.Getenv("MERIDIAN_ADMIN_URL"), os.Getenv("MERIDIAN_INTEGRATOR_KEY"))

	// An external reference makes the create safe to repeat: a second call returns the same organisation.
	ref := "customer-1042"

	org, err := admin.Organisations.Create(ctx, meridian.CreateOrganisation{Name: "Toko Sehat", ExternalReference: &ref})
	if err != nil {
		log.Fatal(err)
	}

	project, err := admin.Projects.Create(ctx, org.ID, meridian.CreateProject{Name: "Chatbot"})
	if err != nil {
		log.Fatal(err)
	}

	issued, err := admin.Keys.Issue(ctx, org.ID, project.ID, meridian.IssueKey{})
	if err != nil {
		log.Fatal(err)
	}

	// issued.APIKey is the only time the key is shown. Hand it to its owner and store it; do not log it.
	log.Println("issued", issued.KeyID, issued.KeyPrefix)
}
The 21 operations
Service Method What it does
Organisations List(ctx) The organisations this integrator created
Create(ctx, CreateOrganisation) Create one; with ExternalReference, a repeat returns the existing one
Get(ctx, org) One organisation by id
GetByReference(ctx, reference) One organisation by your external reference
Projects List(ctx, org) An organisation's projects
Create(ctx, org, CreateProject) Create a project
Get(ctx, org, project) One project
Update(ctx, org, project, UpdateProject) Change exactly one of name, status (active or suspended) or default model (ClearDefaultModel clears it)
Delete(ctx, org, project) Delete a project
Keys List(ctx, org, project) Key metadata, never the secrets
Issue(ctx, org, project, IssueKey) Issue a key; the secret is returned once
Revoke(ctx, org, project, key) Revoke a key
ProviderCredentials List(ctx, org) Stored provider-key metadata, never the keys
Set(ctx, org, provider, SetCredential) Store or replace the organisation's own key for a provider (BYOK)
Delete(ctx, org, provider) Remove it
ProjectModels Get(ctx, org, project) The project's allowed models
Set(ctx, org, project, models) Replace the allowlist
Models List(ctx) The model catalogue, with capabilities and limits
Get(ctx, model) One catalogue model
Analytics Usage(ctx, UsageQuery) A project's requests, tokens and latency (never cost)
Logs List(ctx, LogsQuery) One page of a project's request log; pass NextCursor back while HasMore

There is no supplier catalogue: it carries supplier prices, and no Meridian SDK exposes a price (D-27).

Rules
  • Retries. A write (POST, PUT, PATCH) is retried only after rate_limited, which means the engine did not run it. GET and DELETE retry normally. Organisations.Create retries normally only when ExternalReference is set, because only then is a repeat harmless. Admin calls send no Idempotency-Key.
  • Path safety. An id that is empty, all white space, . or .. (as given, or after one percent decode) is refused with invalid_request and nothing is sent; other ids are escaped as one path segment. by-reference is refused as an organisation id (the engine uses it for GetByReference).
  • Issued-key redaction. IssuedKey and SetCredential print as [redacted] under every fmt verb and as a direct slog attribute. They are not redacted by encoding/json, on purpose, so that json.NewEncoder(w).Encode(issued) can hand the key to its owner. The APIKey field (and SetCredential.Credential) is hidden; KeyID, KeyPrefix and Message are not. A struct, slice or map that holds an IssuedKey is marshalled by encoding/json too, so never log one with a JSON log handler; log KeyID and KeyPrefix. Key and CredentialMetadata (the list results) carry no secret at all.

Retries, timeouts and idempotency

Rule Value
Retries At most WithMaxRetries (default 2, clamped to 0–10)
What is retried Only Retryable errors, only before the response body is read, never mid-stream
Retry-After Honoured (seconds or an HTTP date). A wait over 30 s is not retried
Backoff Without Retry-After: exponential with jitter, 250–500 ms doubling, at most 8 s; stops when the context ends
Header timeout Chat.Create waits up to 300 s for response headers, because the engine sends them only after the whole reply is generated. Chat.Stream, Models.List and the admin client wait up to 60 s
WithHeaderTimeout If set, that value applies to every call. It covers only the wait for headers
Whole-call bound Only your context. Always pass one with a deadline for buffered calls
Idempotency key Chat sends one Idempotency-Key per logical call, the same on every retry. Set ChatParams.IdempotencyKey to choose it: 1–255 bytes, each visible ASCII (0x21–0x7E, no spaces), or the call is refused with invalid_request before anything is sent

If a retry is answered with idempotency_conflict, the engine already has the call's earlier attempt, so you get that attempt's error (its code, message, status and request id), not the conflict. The exception is an earlier rate_limited: the engine releases the key when it refuses before any attempt, so a conflict after one is a genuine conflict and is returned as it came.

Configuration

Setting How Default
Base URL First argument to NewClient / NewAdminClient; absolute http or https, no query, fragment or credentials none (required)
Key Second argument; gmr_… for NewClient, gmi_… for NewAdminClient; visible ASCII, no spaces none (required)
HTTP client WithHTTPClient(*http.Client) (proxies, TLS, tracing, tests); nil is ignored http.DefaultClient
Retries WithMaxRetries(n) 2 (clamped 0–10)
Header timeout WithHeaderTimeout(d); non-positive is ignored 300 s for Chat.Create, otherwise 60 s
User agent WithUserAgentSuffix(s); a byte outside 0x20–0x7E makes the client refuse every call with invalid_request meridian-go/<version>

The SDK reads no environment variable itself. The examples use these names, which are yours to choose:

Variable Used for
MERIDIAN_URL Public listener, for example http://127.0.0.1:8180
MERIDIAN_KEY Project key (gmr_…)
MERIDIAN_ADMIN_URL Admin listener, for example http://127.0.0.1:8181
MERIDIAN_INTEGRATOR_KEY Integrator key (gmi_…)

The contract run reads its own variables; see below.

Contract run (simulator only)

The contract suite (internal/contract, build tag contract) runs the SDK against the real local engine, and only while that engine is in simulator mode: the engine talks to its provider simulator, never to Google or OpenRouter, so the run cannot spend money. go test ./... never builds it. Start it with:

bash scripts/contract.sh      # or: go run ./internal/cmd/contract

Run it in the same environment the engine was started from: the same LOCALAPPDATA (or XDG_STATE_HOME/HOME), and GAUSSMERIDIAN_LOCAL_PROJECT if the engine runs under another compose project than meridian-engine. MERIDIAN_PUBLIC_URL and MERIDIAN_ADMIN_URL default to http://127.0.0.1:8180 and :8181.

The guard (internal/contractguard, a port of the Python guard) decides first and refuses, by name only, never printing a value, when:

  • the runtime folder cannot be located, or MERIDIAN_RUNTIME_DIR, GAUSSMERIDIAN_ENV_FILE or a key file resolves outside it (symlinks and junctions are followed);
  • a paid provider key (GAUSSMERIDIAN_GEMINI_API_KEY, GAUSSMERIDIAN_OPENROUTER_API_KEY, GAUSSMERIDIAN_OPENAI_API_KEY, GAUSSMERIDIAN_ANTHROPIC_API_KEY, GAUSSMERIDIAN_XAI_API_KEY, GAUSSMERIDIAN_DEEPSEEK_API_KEY, GAUSSMERIDIAN_MISTRAL_API_KEY, GAUSSMERIDIAN_QWEN_API_KEY) is set in the process or in any env file the engine or the suite could read (any character outside printable ASCII and tab makes a file uncheckable, NAME='' counts as set, and a missing or non-UTF-8 env file refuses);
  • a URL is not loopback, or a proxy is configured in the environment or the Windows registry;
  • the engine is not proven to be the simulator. Through docker (fixed argv, no shell) it requires: provider-sim running exactly once in the project; exactly one engine container, owning 127.0.0.1:8180 and :8181 (the URLs' ports); the engine's provider settings equal to the simulator's (base URLs at provider-sim:8184, GAUSSMERIDIAN_PROVIDER_SIMULATOR=true, each of the eight paid keys equal to the public dummy sim-local-key), compared inside the docker inspect --format template so only match tokens come out; and the compose label com.docker.compose.project.environment_file naming this runtime's env file. A throwaway LOCALAPPDATA is therefore honoured only when the live engine was started from it.

Then the engine must answer /ready, and the first call (the canary) must return the simulator's fixed reply, sim token 1 … sim token 10 with 10 completion tokens; otherwise the run aborts. The suite covers chat create, an invalid key (invalid_api_key), a stream of exactly 10 text pieces with usage, the sim-fail model's fallback, admin provisioning (organisation, project, key issue, list, revoke) with an organisation of its own, a revoked key refused when used directly, and the forward handler: buffered and streamed against the engine, and the generic 503 for the revoked key. An error frame in the middle of a stream cannot be produced by the simulator and stays in the fake-engine tests. The suite runs offline (GOPROXY=off, GOFLAGS=-mod=readonly) with NO_PROXY=*.

MERIDIAN_GUARD_ORACLES=1 go test -run Oracle ./internal/contractguard/ also checks the guard against copies of the engine's local-up env readers (pwsh and bash).

Development

bash scripts/gate.sh   # gofmt, vet, staticcheck, tests (-race where cgo exists), generated-type drift, stdlib-only check

The race detector needs cgo. The local gate warns and carries on when cgo is missing, so run go test -race ./... in CI.

internal/gen holds types generated from the pinned engine documents in spec/ (bash scripts/generate.sh); they are a test oracle that the hand-written public types are held to, not the public API. meridian.EngineSpecCommit() returns the engine commit they were checked against; support asks for it.

The brand lockups in assets/brand/ ship in the repository, not in any release artefact.

Licence: Apache-2.0. The Meridian logo is a Gaussian trademark; the Apache-2.0 licence covers the code, not the logo.

Documentation

Overview

Package meridian is the Go SDK for the Meridian engine: a server client for chat and models, an admin client for integrators, and errors you match with errors.As.

Keys spend credit. Use this package only on servers; browsers and mobile apps call your own route, built with the forward sub-package.

Index

Constants

View Source
const (
	CodeInvalidRequest          = "invalid_request"
	CodeInvalidAPIKey           = "invalid_api_key"
	CodeKeyRevoked              = "key_revoked"
	CodeKeyExpired              = "key_expired"
	CodeProjectUnavailable      = "project_unavailable"
	CodeOrganisationUnavailable = "organisation_unavailable"
	CodeModelNotAllowed         = "model_not_allowed"
	CodeBYOKRequired            = "byok_required"
	CodeProjectHostMismatch     = "project_host_mismatch"
	CodeModelNotFound           = "model_not_found"
	CodeRouteNotFound           = "route_not_found"
	CodeMethodNotAllowed        = "method_not_allowed"
	CodeCapabilityUnsupported   = "capability_unsupported"
	CodeBodyTooLarge            = "body_too_large"
	CodeRateLimited             = "rate_limited"
	CodeInsufficientCredit      = "insufficient_credit"
	CodeHardLimitReached        = "hard_limit_reached"
	CodeEntitlementMissing      = "entitlement_missing"
	CodeIdempotencyConflict     = "idempotency_conflict"
	CodeUpstreamUnavailable     = "upstream_unavailable"
	CodeUpstreamError           = "upstream_error"
	CodeDeadlineExceeded        = "deadline_exceeded"
	CodeServiceUnavailable      = "service_unavailable"
	CodeInternal                = "internal"
	CodeInvalidToken            = "invalid_token"
	CodePermissionDenied        = "permission_denied"
	CodeNotFound                = "not_found"
	CodeConflict                = "conflict"
)

Engine error codes: the engine's closed list, in the engine's order.

View Source
const (
	CodeNetworkError = "network_error"
	CodeAborted      = "aborted"
)

SDK-only codes. The SDK adds no others.

View Source
const (
	TypeInvalidRequest = "invalid_request_error"
	TypeAuthentication = "authentication_error"
	TypePermission     = "permission_error"
	TypeRateLimit      = "rate_limit_error"
	TypeAPI            = "api_error"
	TypeClient         = "client_error"
)

Error types: the engine's five, and client_error for failures inside the SDK.

View Source
const Version = "0.1.0"

Version is the SDK version sent in the User-Agent header.

Variables

This section is empty.

Functions

func EngineCodes

func EngineCodes() []string

EngineCodes returns the engine's 28 error codes in the engine's order.

func EngineSpecCommit

func EngineSpecCommit() string

EngineSpecCommit is the engine commit whose OpenAPI documents this SDK's types were checked against. Support asks for it.

func EngineTypes

func EngineTypes() []string

EngineTypes returns the engine's five error types.

func WriteError

func WriteError(w http.ResponseWriter, err error)

WriteError answers an HTTP request with err as the engine's error envelope: the error's own code, message and request id, with its HTTP status (502 when it has none, as a network or configuration error does, or when the status is not an error status). It hides nothing, and the SDK-only type client_error is written as api_error. An error that is not a *Error is an internal error with no detail (so is a nil *Error, however it is wrapped). A request id that is not printable ASCII (1–255 bytes) is left out of the header and the body. A rate_limited error that came from the engine with a valid Retry-After (seconds or an HTTP date) repeats it.

A route that a browser or an app calls must not show it why your server failed (a missing key, an unreachable engine, your own account's credit): use package forward, which applies the generic 503, or choose what to show yourself.

Types

type AdminClient

type AdminClient struct {
	Organisations       *Organisations
	Projects            *Projects
	Keys                *Keys
	ProviderCredentials *ProviderCredentials
	ProjectModels       *ProjectModels
	Models              *AdminModels
	Analytics           *Analytics
	Logs                *Logs
}

AdminClient provisions Meridian for an integrator with a gmi_ key, on the engine's admin address (a separate listener from the one chat uses). Provisioning belongs on your server: never put a gmi_ key in a browser or an app. It is safe for concurrent use.

It wraps the engine's integrator operations except the supplier-catalogue ones, which return only supplier prices; no SDK exposes a price (D-27, owner decision of 10 October 2026), so billing reads the catalogue from the engine's admin API directly. An organisation is renamed and deleted by a person in the console, not through an integrator key.

The zero value, and a nil service, return an invalid_request error from every call.

func NewAdminClient

func NewAdminClient(baseURL, key string, opts ...Option) *AdminClient

NewAdminClient returns an admin client for the engine's admin address, with an integrator key (gmi_…). It never fails: a bad URL or empty key gives a client whose calls return an invalid_request error and send nothing.

type AdminModels

type AdminModels struct {
	// contains filtered or unexported fields
}

AdminModels reads the model catalogue.

func (*AdminModels) Get

func (s *AdminModels) Get(ctx context.Context, model string) (*CatalogueModel, error)

Get returns one catalogue model.

func (*AdminModels) List

func (s *AdminModels) List(ctx context.Context) ([]CatalogueModel, error)

List returns the catalogue's models.

type Analytics

type Analytics struct {
	// contains filtered or unexported fields
}

Analytics reads usage.

func (*Analytics) Usage

func (s *Analytics) Usage(ctx context.Context, q UsageQuery) (*UsageReport, error)

Usage returns a project's usage in a window: requests, tokens and latency.

type AssistantMessage

type AssistantMessage struct {
	Role             string     `json:"role"`
	Content          *string    `json:"content"`
	Reasoning        *string    `json:"reasoning,omitempty"`
	ReasoningContent *string    `json:"reasoning_content,omitempty"`
	Refusal          *string    `json:"refusal,omitempty"`
	ToolCalls        []ToolCall `json:"tool_calls,omitempty"`
}

AssistantMessage is the model's message.

type CatalogueModel

type CatalogueModel struct {
	ID              string            `json:"id"`
	Provider        string            `json:"provider"`
	Capabilities    ModelCapabilities `json:"capabilities"`
	ContextTokens   int64             `json:"context_tokens"`
	MaxOutputTokens int64             `json:"max_output_tokens"`
}

CatalogueModel is a model in the catalogue, with what it supports.

type ChatChoice

type ChatChoice struct {
	Index        int              `json:"index"`
	Message      AssistantMessage `json:"message"`
	FinishReason string           `json:"finish_reason"`
}

ChatChoice is one choice of a buffered reply.

type ChatChunk

type ChatChunk struct {
	ID      string        `json:"id"`
	Object  string        `json:"object"`
	Created int64         `json:"created"`
	Model   string        `json:"model"`
	Choices []ChunkChoice `json:"choices"`
	Usage   *Usage        `json:"usage,omitempty"`
}

ChatChunk is one streamed chunk.

func (ChatChunk) Text

func (c ChatChunk) Text() string

Text is the first choice's new content, or "".

type ChatCompletion

type ChatCompletion struct {
	ID      string       `json:"id"`
	Object  string       `json:"object"`
	Created int64        `json:"created"`
	Model   string       `json:"model"`
	Choices []ChatChoice `json:"choices"`
	Usage   Usage        `json:"usage"`

	// RequestID is the engine's request id (the X-Request-Id header), not part of the reply body.
	RequestID string `json:"-"`
}

ChatCompletion is a buffered chat reply.

func (*ChatCompletion) Text

func (c *ChatCompletion) Text() string

Text is the first choice's content, or "".

type ChatMessage

type ChatMessage struct {
	Role       Role       `json:"role"`
	Content    any        `json:"content,omitempty"`
	Name       string     `json:"name,omitempty"`
	ToolCallID string     `json:"tool_call_id,omitempty"`
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
}

ChatMessage is one message. Content is a string, a []ContentPart, or nil.

type ChatParams

type ChatParams struct {
	Messages            []ChatMessage `json:"messages,omitempty"`
	Model               string        `json:"model,omitempty"`
	Temperature         *float64      `json:"temperature,omitempty"`
	TopP                *float64      `json:"top_p,omitempty"`
	MaxTokens           *int          `json:"max_tokens,omitempty"`
	MaxCompletionTokens *int          `json:"max_completion_tokens,omitempty"`
	N                   *int          `json:"n,omitempty"`
	Stop                any           `json:"stop,omitempty"`
	Tools               []Tool        `json:"tools,omitempty"`
	ToolChoice          any           `json:"tool_choice,omitempty"`
	ResponseFormat      any           `json:"response_format,omitempty"`
	User                string        `json:"user,omitempty"`

	// IdempotencyKey is sent on every retry of this call; empty means the SDK generates one. It
	// must be 1–255 bytes, each visible ASCII (0x21–0x7E, so no spaces), or the call is refused
	// with invalid_request before anything is sent.
	IdempotencyKey string `json:"-"`
	// Extra holds request fields the engine forwards to the provider. Known fields and the
	// stream flags always win over Extra.
	Extra map[string]any `json:"-"`
}

ChatParams is a chat request. Leave Model empty to let Meridian choose (D-25).

type ChatService

type ChatService struct {
	// contains filtered or unexported fields
}

ChatService sends chat requests. Its zero value, or a nil *ChatService, returns an invalid_request error from every call instead of panicking.

func (*ChatService) Create

func (s *ChatService) Create(ctx context.Context, p ChatParams) (*ChatCompletion, error)

Create sends a chat request and waits for the whole reply. The engine sends headers only after the whole reply is generated, so Create waits up to 300 s for them (Stream and the admin client 60 s); WithHeaderTimeout, if given, replaces that for every call. Bound the call with ctx.

func (*ChatService) Stream

func (s *ChatService) Stream(ctx context.Context, p ChatParams) (*ChatStream, error)

Stream sends a chat request and returns the reply as a stream. Usage is always requested. Cancelling ctx stops the stream, so a handler relaying to a browser should pass r.Context(). Close a stream you do not read.

type ChatStream

type ChatStream struct {
	// contains filtered or unexported fields
}

ChatStream is one streamed chat reply. Read it once, with Chunks, Text or ServeSSE. Reading to the end or breaking out of Chunks releases the connection; a stream that is never read must be released with Close.

func (*ChatStream) Chunks

func (s *ChatStream) Chunks() iter.Seq2[ChatChunk, error]

Chunks yields the reply chunk by chunk. A failure arrives as the error of the last iteration. Breaking out of the loop closes the stream and cancels the upstream request.

func (*ChatStream) Close

func (s *ChatStream) Close() error

Close stops the stream and cancels the upstream request. It is safe to call more than once, and does nothing on a stream that was not returned by Chat.Stream (a zero or nil *ChatStream).

func (*ChatStream) RequestID

func (s *ChatStream) RequestID() string

RequestID is the engine's request id for this stream; empty when the engine sent none, or one that is not printable ASCII (1–255 bytes).

func (*ChatStream) ServeSSE

func (s *ChatStream) ServeSSE(w http.ResponseWriter, mapError func(*Error) *Error) error

ServeSSE relays the stream to w byte for byte, flushing after each event, and returns the real error that ended it (nil after [DONE]). Use it from your own route handler.

mapError, if not nil, sees every error that ends the stream: an engine error frame, a malformed frame, a dropped connection or a cancellation. It receives a copy; any non-nil *Error it returns (a new one, or the copy edited in place) is relayed as a final error frame instead of the original, and the frame that carried the original error is never written. Returning nil leaves an engine error frame as it came. With a nil mapError engine error frames are relayed as they came, and an SDK-side failure writes nothing more (the stream just stops).

Pass the incoming request's context (r.Context()) to Chat.Stream so a browser disconnect cancels the upstream request.

func (*ChatStream) Text

func (s *ChatStream) Text() (string, error)

Text collects the whole reply. On failure it returns the text so far and the error.

func (*ChatStream) Usage

func (s *ChatStream) Usage() *Usage

Usage is the token counts reported at the end of the stream, or nil before then.

type ChunkChoice

type ChunkChoice struct {
	Index        int        `json:"index"`
	Delta        ChunkDelta `json:"delta"`
	FinishReason *string    `json:"finish_reason"`
}

ChunkChoice is one choice of a chunk.

type ChunkDelta

type ChunkDelta struct {
	Role             *string         `json:"role,omitempty"`
	Content          *string         `json:"content,omitempty"`
	Reasoning        *string         `json:"reasoning,omitempty"`
	ReasoningContent *string         `json:"reasoning_content,omitempty"`
	Refusal          *string         `json:"refusal,omitempty"`
	ToolCalls        []ToolCallDelta `json:"tool_calls,omitempty"`
}

ChunkDelta is what a chunk adds.

type Client

type Client struct {
	Chat   *ChatService
	Models *ModelsService
}

Client is the server client for chat and models. It is safe for concurrent use.

func NewClient

func NewClient(baseURL, key string, opts ...Option) *Client

NewClient returns a client for the engine at baseURL with a project key (gmr_…). It never fails: a bad URL or empty key gives a client whose calls return an invalid_request error.

type ContentPart

type ContentPart struct {
	Type     string    `json:"type"`
	Text     string    `json:"text,omitempty"`
	ImageURL *ImageURL `json:"image_url,omitempty"`
}

ContentPart is one part of a multi-part message.

func ImagePart

func ImagePart(url string) ContentPart

ImagePart is an image content part (an https or data URL).

func TextPart

func TextPart(text string) ContentPart

TextPart is a text content part.

type CreateOrganisation

type CreateOrganisation struct {
	Name              string  `json:"name"`
	Slug              *string `json:"slug,omitempty"`
	ExternalReference *string `json:"external_reference,omitempty"`
}

CreateOrganisation creates an organisation. With an ExternalReference a repeated create returns the existing organisation, so the SDK can safely retry it.

type CreateProject

type CreateProject struct {
	Name string `json:"name"`
}

CreateProject creates a project.

type CredentialMetadata

type CredentialMetadata struct {
	Provider   string  `json:"provider"`
	Hint       string  `json:"hint"`
	CreatedAt  string  `json:"created_at"`
	LastUsedAt *string `json:"last_used_at"`
	Policy     string  `json:"policy"` // "prefer" or "require"
}

CredentialMetadata describes a stored provider key without revealing it.

type Error

type Error struct {
	// Code is one of the engine's 28 codes, or CodeNetworkError or CodeAborted.
	Code string
	// Type is one of the engine's five types, or TypeClient for failures inside the SDK.
	Type string
	// Status is the HTTP status of the engine's reply. It is 0 when no HTTP reply arrived, and for
	// an SDK-side failure inside a stream that had begun (a truncation, an unreadable frame);
	// engine error frames carry the status for their code. A malformed or unreadable reply keeps
	// its real status.
	Status int
	// Message is the engine's message, or the SDK's own explanation.
	Message string
	// Retryable reports whether the same call could succeed if tried again.
	Retryable bool
	// RequestID is the engine's request id, when known. Give it to Gaussian support.
	RequestID string
	// EngineCode is the engine's own code when this SDK does not recognise it (a newer engine);
	// Code is then CodeInternal. Empty otherwise.
	EngineCode string
	// contains filtered or unexported fields
}

Error is the only error type the SDK returns. Find it with errors.As.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause, such as context.Canceled, so errors.Is works.

type ImageURL

type ImageURL struct {
	URL    string `json:"url"`
	Detail string `json:"detail,omitempty"`
}

ImageURL is an image part's source. Images need a vision model.

type IssueKey

type IssueKey struct {
	Name               *string `json:"name,omitempty"`
	RateLimitPerMinute *int64  `json:"rate_limit_per_minute,omitempty"`
	RateLimitPerDay    *int64  `json:"rate_limit_per_day,omitempty"`
	ExpiresInDays      *int64  `json:"expires_in_days,omitempty"`
}

IssueKey issues a project key. Every field is optional.

type IssuedKey

type IssuedKey struct {
	KeyID     string `json:"key_id"`
	APIKey    string `json:"api_key"`
	KeyPrefix string `json:"key_prefix"`
	Message   string `json:"message"`
}

IssuedKey is a new key. APIKey is the secret: the engine shows it once, so store it now.

It is redacted under every fmt verb (%v, %+v, %#v, %s, %q, %x, %d and the rest) and as a direct log/slog attribute (slog.Any("key", issued)). It is NOT redacted by encoding/json, on purpose: handing a new key back to its owner is the point of Keys.Issue, so json.NewEncoder(w).Encode(issued) carries APIKey. A struct, slice or map that holds an IssuedKey is marshalled by encoding/json too, so slog's JSON handler logs the secret from one: do not log a struct that holds an IssuedKey with a JSON handler; log KeyID and KeyPrefix. (fmt prints a misused operand raw, without calling any method: a value given to %p and a non-error given to %w. go vet reports both.)

func (IssuedKey) Format

func (k IssuedKey) Format(f fmt.State, _ rune)

Format prints the redacted form under every fmt verb, so no verb reaches the secret.

func (IssuedKey) LogValue

func (k IssuedKey) LogValue() slog.Value

LogValue redacts the secret for log/slog.

func (IssuedKey) String

func (k IssuedKey) String() string

String redacts the secret.

type Key

type Key struct {
	ID                 string  `json:"id"`
	ProjectID          string  `json:"project_id"`
	KeyPrefix          string  `json:"key_prefix"`
	Name               *string `json:"name"`
	RateLimitPerMinute *int64  `json:"rate_limit_per_minute"`
	RateLimitPerDay    *int64  `json:"rate_limit_per_day"`
	CreatedAt          string  `json:"created_at"`
	ExpiresAt          *string `json:"expires_at"`
	LastUsedAt         *string `json:"last_used_at"`
	Active             bool    `json:"active"`
}

Key is a project key's metadata. The secret is never listed.

type Keys

type Keys struct {
	// contains filtered or unexported fields
}

Keys manages a project's keys.

func (*Keys) Issue

func (s *Keys) Issue(ctx context.Context, org, project string, in IssueKey) (*IssuedKey, error)

Issue issues a key and returns its secret once: store IssuedKey.APIKey now. It is retried only when the engine rate-limits it, so a dropped connection never makes a second key.

func (*Keys) List

func (s *Keys) List(ctx context.Context, org, project string) ([]Key, error)

List returns a project's keys, never their secrets.

func (*Keys) Revoke

func (s *Keys) Revoke(ctx context.Context, org, project, key string) error

Revoke revokes a key.

type LogPage

type LogPage struct {
	Logs       []LogRow `json:"logs"`
	HasMore    bool     `json:"has_more"`
	NextCursor *string  `json:"next_cursor"`
}

LogPage is one page of the request log. Pass NextCursor back as LogsQuery.Cursor while HasMore.

type LogRow

type LogRow struct {
	ID        string  `json:"id"`
	Model     *string `json:"model"`
	Provider  *string `json:"provider"`
	TokensIn  *int64  `json:"tokens_in"`
	TokensOut *int64  `json:"tokens_out"`
	LatencyMs int64   `json:"latency_ms"`
	CreatedAt string  `json:"created_at"`
	Status    string  `json:"status"`
}

LogRow is one request in the log: tokens and latency, never a cost.

type Logs

type Logs struct {
	// contains filtered or unexported fields
}

Logs reads the request log.

func (*Logs) List

func (s *Logs) List(ctx context.Context, q LogsQuery) (*LogPage, error)

List returns one page of a project's request log.

type LogsQuery

type LogsQuery struct {
	ProjectID string
	PageSize  int
	Cursor    string
}

LogsQuery pages through a project's request log. ProjectID is required; PageSize 0 leaves the size to the engine, and Cursor is a previous page's NextCursor.

type Model

type Model struct {
	ID      string `json:"id"`
	Object  string `json:"object"`
	OwnedBy string `json:"owned_by"`
}

Model is a model the key may use.

type ModelCapabilities

type ModelCapabilities struct {
	Streaming  bool `json:"streaming"`
	Tools      bool `json:"tools"`
	Vision     bool `json:"vision"`
	JSONOutput bool `json:"json_output"`
}

ModelCapabilities is what a catalogue model supports.

type ModelPerformance

type ModelPerformance struct {
	Model    string `json:"model"`
	Requests int64  `json:"requests"`
	Tokens   *int64 `json:"tokens"`
}

ModelPerformance is one model's share of the usage.

type ModelsService

type ModelsService struct {
	// contains filtered or unexported fields
}

ModelsService lists models. Like ChatService, its zero value or nil pointer returns an invalid_request error instead of panicking.

func (*ModelsService) List

func (s *ModelsService) List(ctx context.Context) ([]Model, error)

List returns the models the key may use.

type Option

type Option func(*config)

Option configures a client.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient sets the *http.Client used for every request (proxies, TLS, tracing, tests). A nil client is ignored.

func WithHeaderTimeout

func WithHeaderTimeout(d time.Duration) Option

WithHeaderTimeout bounds each attempt's wait for response headers, for every call. Without it the wait is 60 s, except for Chat.Create, which waits 300 s because the engine sends headers only after the whole reply is generated. A value that is not positive is ignored. The whole call is bounded only by the context.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a retryable failure is retried before the body is read. Values are clamped to 0–10. The default is 2.

func WithUserAgentSuffix

func WithUserAgentSuffix(s string) Option

WithUserAgentSuffix appends s to the User-Agent header. A suffix with a byte outside 0x20–0x7E (a newline, a tab, a non-ASCII character) cannot be sent intact: the client is then refused, like one with a bad key, and every call returns invalid_request without sending anything.

type Organisation

type Organisation struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	Slug         string `json:"slug"`
	Status       string `json:"status"` // "active", "suspended" or "locked"
	CreatedAt    string `json:"created_at"`
	UpdatedAt    string `json:"updated_at"`
	MemberCount  int64  `json:"member_count"`
	ProjectCount int64  `json:"project_count"`
}

Organisation is a Meridian organisation.

type Organisations

type Organisations struct {
	// contains filtered or unexported fields
}

Organisations manages organisations.

func (*Organisations) Create

Create creates an organisation. With an ExternalReference a repeat returns the existing one, so only then is the call retried after a failure that might have reached the engine.

func (*Organisations) Get

func (s *Organisations) Get(ctx context.Context, org string) (*Organisation, error)

Get returns an organisation by id.

func (*Organisations) GetByReference

func (s *Organisations) GetByReference(ctx context.Context, reference string) (*Organisation, error)

GetByReference returns the organisation you created with this external reference.

func (*Organisations) List

func (s *Organisations) List(ctx context.Context) ([]Organisation, error)

List returns the organisations this integrator created.

type Project

type Project struct {
	ID           string  `json:"id"`
	Ref          string  `json:"ref"`
	Name         string  `json:"name"`
	OrgID        string  `json:"org_id"`
	Status       string  `json:"status"`
	DefaultModel *string `json:"default_model"`
	CreatedAt    string  `json:"created_at"`
}

Project is a project in an organisation.

type ProjectModels

type ProjectModels struct {
	// contains filtered or unexported fields
}

ProjectModels manages the models a project may use.

func (*ProjectModels) Get

func (s *ProjectModels) Get(ctx context.Context, org, project string) ([]string, error)

Get returns the project's allowed models.

func (*ProjectModels) Set

func (s *ProjectModels) Set(ctx context.Context, org, project string, models []string) ([]string, error)

Set replaces the project's allowed models.

type Projects

type Projects struct {
	// contains filtered or unexported fields
}

Projects manages an organisation's projects.

func (*Projects) Create

func (s *Projects) Create(ctx context.Context, org string, in CreateProject) (*Project, error)

Create creates a project. It is retried only when the engine rate-limits it.

func (*Projects) Delete

func (s *Projects) Delete(ctx context.Context, org, project string) error

Delete deletes a project.

func (*Projects) Get

func (s *Projects) Get(ctx context.Context, org, project string) (*Project, error)

Get returns a project.

func (*Projects) List

func (s *Projects) List(ctx context.Context, org string) ([]Project, error)

List returns an organisation's projects.

func (*Projects) Update

func (s *Projects) Update(ctx context.Context, org, project string, in UpdateProject) (*Project, error)

Update changes one project field: its name, its status or its default model.

type ProviderCredentials

type ProviderCredentials struct {
	// contains filtered or unexported fields
}

ProviderCredentials manages an organisation's own provider keys (BYOK).

func (*ProviderCredentials) Delete

func (s *ProviderCredentials) Delete(ctx context.Context, org, provider string) error

Delete removes the organisation's key for a provider.

func (*ProviderCredentials) List

List returns the stored provider keys' metadata, never the keys.

func (*ProviderCredentials) Set

func (s *ProviderCredentials) Set(ctx context.Context, org, provider string, in SetCredential) (*CredentialMetadata, error)

Set stores or replaces the organisation's key for a provider, such as "gemini".

type Role

type Role string

Role is a message role. The engine forwards any role as sent; these are the ones providers accept.

const (
	RoleSystem    Role = "system"
	RoleDeveloper Role = "developer"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

Message roles.

type SetCredential

type SetCredential struct {
	Credential string  `json:"credential"`
	Policy     *string `json:"policy,omitempty"`
}

SetCredential stores an organisation's own provider key (BYOK). Policy is "prefer" or "require"; leave it nil for the engine's default.

Like IssuedKey it is redacted under every fmt verb and as a direct log/slog attribute, and NOT redacted by encoding/json (the request body needs the credential). Do not log a struct that holds a SetCredential with slog's JSON handler.

func (SetCredential) Format

func (c SetCredential) Format(f fmt.State, _ rune)

Format prints the redacted form under every fmt verb, so no verb reaches the credential.

func (SetCredential) LogValue

func (c SetCredential) LogValue() slog.Value

LogValue redacts the credential for log/slog.

func (SetCredential) String

func (c SetCredential) String() string

String redacts the credential.

type TimeRange

type TimeRange struct {
	Start string `json:"start"`
	End   string `json:"end"`
}

TimeRange is the window a usage report covers (RFC 3339, UTC).

type Tool

type Tool struct {
	Type     string       `json:"type"`
	Function ToolFunction `json:"function"`
}

Tool is a function the model may call.

func FunctionTool

func FunctionTool(name, description string, parameters any) Tool

FunctionTool is a function tool.

type ToolCall

type ToolCall struct {
	ID       string           `json:"id"`
	Type     string           `json:"type"`
	Function ToolCallFunction `json:"function"`
}

ToolCall is a call the model made.

type ToolCallDelta

type ToolCallDelta struct {
	Index    int                    `json:"index"`
	ID       *string                `json:"id,omitempty"`
	Type     *string                `json:"type,omitempty"`
	Function *ToolCallDeltaFunction `json:"function,omitempty"`
}

ToolCallDelta is part of a streamed tool call.

type ToolCallDeltaFunction

type ToolCallDeltaFunction struct {
	Name      *string `json:"name,omitempty"`
	Arguments *string `json:"arguments,omitempty"`
}

ToolCallDeltaFunction is part of a streamed function call.

type ToolCallFunction

type ToolCallFunction struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

ToolCallFunction is the called function and its JSON arguments.

type ToolFunction

type ToolFunction struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Parameters  any    `json:"parameters,omitempty"`
}

ToolFunction describes a callable function. Parameters is a JSON Schema object.

type UpdateProject

type UpdateProject struct {
	Name              *string `json:"name,omitempty"`
	Status            *string `json:"status,omitempty"`
	DefaultModel      *string `json:"default_model,omitempty"`
	ClearDefaultModel bool    `json:"-"`
}

UpdateProject changes exactly one of Name, Status ("active" or "suspended") or DefaultModel. Set ClearDefaultModel to clear the default model (the engine reads JSON null as "clear"); setting it together with DefaultModel is refused as an invalid_request.

func (UpdateProject) MarshalJSON

func (u UpdateProject) MarshalJSON() ([]byte, error)

MarshalJSON sends "default_model": null when ClearDefaultModel is set.

type Usage

type Usage struct {
	PromptTokens     *int `json:"prompt_tokens,omitempty"`
	CompletionTokens *int `json:"completion_tokens,omitempty"`
	TotalTokens      *int `json:"total_tokens,omitempty"`
}

Usage is the reply's token counts. A count the engine did not report is nil, never 0. There is never a price or cost (D-27).

type UsageQuery

type UsageQuery struct {
	ProjectID, From, To string
}

UsageQuery selects a usage window. ProjectID is required; From and To are RFC 3339 times and optional.

type UsageReport

type UsageReport struct {
	Summary          UsageSummary       `json:"summary"`
	ModelPerformance []ModelPerformance `json:"model_performance"`
	TimeRange        TimeRange          `json:"time_range"`
}

UsageReport is a project's usage in a window: requests, tokens and latency, never cost.

type UsageSummary

type UsageSummary struct {
	TotalRequests         int64   `json:"total_requests"`
	SuccessfulRequests    int64   `json:"successful_requests"`
	TotalTokens           *int64  `json:"total_tokens"`
	AverageLatencyMs      int64   `json:"average_latency_ms"`
	P95LatencyMs          int64   `json:"p95_latency_ms"`
	P99LatencyMs          int64   `json:"p99_latency_ms"`
	PercentileSampleCount int64   `json:"percentile_sample_count"`
	PercentilesSampled    bool    `json:"percentiles_sampled"`
	SuccessRate           float64 `json:"success_rate"`
	ErrorRate             float64 `json:"error_rate"`
}

UsageSummary totals a usage window. TotalTokens is nil when any request's token count is unknown.

Directories

Path Synopsis
Package forward is a ready http.Handler that forwards a browser's or mobile app's chat to Meridian with your server's key.
Package forward is a ready http.Handler that forwards a browser's or mobile app's chat to Meridian with your server's key.
internal
cmd/contract command
Command contract runs the contract suite against the real local engine, in simulator mode only.
Command contract runs the contract suite against the real local engine, in simulator mode only.
cmd/downconvert command
Command downconvert rewrites an OpenAPI 3.1 document into the OpenAPI 3.0 subset that oapi-codegen turns into plain standard-library Go.
Command downconvert rewrites an OpenAPI 3.1 document into the OpenAPI 3.0 subset that oapi-codegen turns into plain standard-library Go.
cmd/pinspec command
Command pinspec copies the engine's committed OpenAPI documents into spec/ and records the engine commit in spec/SOURCE.json.
Command pinspec copies the engine's committed OpenAPI documents into spec/ and records the engine commit in spec/SOURCE.json.
contract
Package contract holds the contract suite: the SDK against the real local engine in simulator mode.
Package contract holds the contract suite: the SDK against the real local engine in simulator mode.
contractguard
Package contractguard is the contract guard: a contract run talks to the real local engine and must never spend money, so it may start only when the engine is proven to be the provider simulator, started from this runtime's env file, with no paid key anywhere it could read one.
Package contractguard is the contract guard: a contract run talks to the real local engine and must never spend money, so it may start only when the engine is proven to be the provider simulator, started from this runtime's env file, with no paid key anywhere it could read one.
fakeengine
Package fakeengine is an in-memory Meridian engine for the SDK's tests.
Package fakeengine is an in-memory Meridian engine for the SDK's tests.
gen/admin
Package admin provides primitives to interact with the openapi HTTP API.
Package admin provides primitives to interact with the openapi HTTP API.
gen/public
Package public holds types generated by oapi-codegen from the pinned public engine document.
Package public holds types generated by oapi-codegen from the pinned public engine document.
idemkey
Package idemkey holds the one rule for a valid Idempotency-Key, shared by the root package (a caller's ChatParams.IdempotencyKey) and package forward (a browser's header), so the two cannot drift apart.
Package idemkey holds the one rule for a valid Idempotency-Key, shared by the root package (a caller's ChatParams.IdempotencyKey) and package forward (a browser's header), so the two cannot drift apart.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL