Files
sm-golem-plugin/README.md
Jakub Zych cd3f0f6c1c feat: golem15.golem plugin with the OpenAI-compatible adapter and AIService.Send
- Prompt, Payload and AIResponse value objects ported from Golem15.Golem
- OpenAIAdapter: chat completions mapping with PHP's response_format,
  max_completion_tokens and reasoning_effort rules
- AIService.Send through fetchguard (TrustedMode for operator models,
  PublicOnlyMode otherwise) with PHP's failure messages
- config defaults for the SSRF allowlist and the AI timeout, lang, README
2026-10-03 22:41:49 +02:00

5.1 KiB

golem15.golem

The shared AI plugin for SummerCMS applications: provider adapters for the Anthropic Messages API and the OpenAI-compatible chat completions API, prompts and AI responses, and an AI service that sends them through the framework's guarded outbound client.

import _ "git.golem15.com/golem15/sm-golem-plugin"

Module git.golem15.com/golem15/sm-golem-plugin, package golem, plugin ID golem15.golem.

Overview

The plugin is a compiled SummerCMS plugin ported from the WinterCMS Golem15.Golem plugin. Its init registers it with the framework, so an application loads it by listing it in summer.yaml; summer build then emits the blank import above into the application's plugins.gen.go. Another plugin that sends AI requests lists golem15.golem in its Requires() and imports the classes/... packages.

The adapters are hand-written JSON mappings with no vendor SDK. Every request goes through a fetchguard.Client: a config marked Trusted (a model an operator configured) runs in fetchguard.TrustedMode, every other config (a key a user or an organisation supplied) in fetchguard.PublicOnlyMode, which refuses private and reserved addresses at dial time.

The plugin's config keys, lang namespace and plugin ID are a contract shared by every application that mounts it: change them only in a backwards-compatible way.

Features

  • Prompts. valueobjects.Prompt is an immutable request: a system prompt, user and assistant messages, messages with base64 images (AddUserMessageWithImage, AddUserMessageWithImages) or an uploaded file id (AddUserMessageWithFileID), a model override and extra options in insertion order. ToAPIPayload builds the OpenAI chat completions body as a valueobjects.Payload, a JSON object that keeps its key order.
  • Responses. valueobjects.AIResponse carries the content, token usage and the provider's decoded answer (Raw, which keeps finish_reason and stop_reason) or the failure message. Err holds the Go error behind a failure for logs and tests and is never shown to a user.
  • OpenAI-compatible adapter. providers.OpenAIAdapter posts to <base>/chat/completions with Authorization: Bearer, drops response_format, maps max_tokens to max_completion_tokens and adds reasoning_effort: low for o-series and gpt-5 models. It also serves compatible providers through their base URL. providers.ForName picks the adapter by name.
  • AI service. services.AIService.Send applies the model's system prompt when the prompt has none and posts the prompt to the config's chat endpoint. It returns the WinterCMS plugin's failure messages: no model configured, no API key, no connection, and an answer that is not JSON. services.ForApp returns the application's shared service.

Usage

Mount the plugin in an application as a git submodule at plugins/golem15/golem:

git submodule add git@git.golem15.com:golem15/sm-golem-plugin.git plugins/golem15/golem

Add it to the application's go.work and go.mod:

use ./plugins/golem15/golem

require git.golem15.com/golem15/sm-golem-plugin v0.0.0

replace git.golem15.com/golem15/sm-golem-plugin => ./plugins/golem15/golem

List it in summer.yaml before the plugins that require it and run summer build:

module: git.golem15.com/golem15/acme
binary: acme
plugins:
  - id: golem15.golem
    module: git.golem15.com/golem15/sm-golem-plugin
  - id: acme.blog
    module: git.golem15.com/golem15/acme/plugins/acme/blog

The plugin's own go.mod replaces the framework with ../../../../summercms.go, so it expects the framework checkout next to the application checkout.

A plugin sends a prompt with a user's own key:

svc := services.ForApp(app)
prompt := valueobjects.NewPrompt().
	WithSystem("You catalog physical items.").
	AddUserMessage("List the items.")
res := svc.Send(ctx, prompt, &services.ModelConfig{
	Adapter: "openai",
	APIKey:  key,
	Model:   "gpt-4o-mini",
})
if !res.Success {
	return errors.New(res.Error)
}

API reference

  • golem: Plugin, the golem15.golem plugin.
  • classes/valueobjects: Prompt, NewPrompt, Message, ContentPart, ImageURL, FileRef, Image, Payload, NewPayload, AIResponse, Success, Failure.
  • classes/providers: Adapter, OpenAIAdapter, ForName, OpenAIBaseURL, AnthropicBaseURL.
  • classes/services: ModelConfig, DefaultModelID, AIService, NewAIService, ForApp, ConfigTimeoutSeconds, DefaultTimeout.

Configuration

Defaults live in config/config.yaml and are merged under golem15.golem.*.

Key Default Meaning
golem15.golem.ssrf.allowed_hosts oaidalleapiprodscus.blob.core.windows.net, .openai.com Hosts an untrusted base URL or an AI-returned image URL may use. A leading dot matches any subdomain.
golem15.golem.ai.timeout_seconds 120 Timeout of every AI provider request.

Dependencies

  • The SummerCMS framework, git.golem15.com/golem15/summercms (modules backpack, compass, fetchguard, pact, party).
  • GORM, as pinned in go.mod.

Testing

go test ./...