Help us shape the future of Go! Take the 2026 Go Developer Survey

fmgo

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

fmgo

fmgo

A Go interface for Apple's Foundation Models CLI (fm).

Go Reference Apache-2.0 license Go version

fmgo provides an idiomatic Go interface to Apple's native Foundation Models CLI (fm). It executes and manages the native command internally so Go applications can use Apple's on-device Foundation Model without Swift, cgo, or private framework bindings.

It is a focused Go library: not a CLI, Apple SDK, Swift bridge, cgo bridge, or reimplementation of Foundation Models.

Features

  • Non-streaming and streaming response generation with context cancellation
  • Instructions, text segments, image input, transcripts, and conversation resume
  • Structured output with local SchemaFor[T] generation and RespondAs[T]
  • Token counting and structured Foundation Model availability results
  • Interactive native fm chat process/session management
  • Native fm serve lifecycle management over TCP or Unix sockets
  • Typed sentinel errors and diagnostic CommandError values

Requirements

  • macOS 27 or later
  • Go 1.27 or later
  • Apple's native fm executable
  • Foundation Models available on the machine
  • Accepted Foundation Models CLI terms where required

fmgo.New returns ErrUnsupportedPlatform outside macOS and ErrUnsupportedVersion before macOS 27. Operations return ErrFMNotFound when the native executable cannot be located.

Installation

go get github.com/ruanklein/fmgo

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/ruanklein/fmgo"
)

func main() {
	client, err := fmgo.New()
	if err != nil {
		log.Fatal(err)
	}

	response, err := client.Respond(context.Background(), fmgo.Request{
		Prompt: "Explain goroutines in one paragraph.",
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(response.Text)
}

Usage

The examples below assume a previously created client and ctx.

Instructions

response, err := client.Respond(ctx, fmgo.Request{
	Prompt:       "Explain channels.",
	Instructions: "Use one concise paragraph.",
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Text)

Streaming

Stream owns a native process. Always close it and check its terminal error.

stream, err := client.Stream(ctx, fmgo.Request{
	Prompt: "Write a short story.",
})
if err != nil {
	log.Fatal(err)
}
defer stream.Close()

for stream.Next() {
	fmt.Print(stream.Text())
}
if err := stream.Err(); err != nil {
	log.Fatal(err)
}

Images

Image paths are passed directly to the native fm command. fmgo does not process the image itself.

response, err := client.Respond(ctx, fmgo.Request{
	Prompt: "Describe this image.",
	Images: []string{"/path/to/image.png"},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Text)

Structured Output

SchemaFor generates JSON Schema locally from a Go type. RespondAs uses that schema for the request and decodes the JSON response into the same type.

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

schema, err := fmgo.SchemaFor[Person]()
if err != nil {
	log.Fatal(err)
}

response, err := client.Respond(ctx, fmgo.Request{
	Prompt: "Generate a fictional person.",
	Schema: schema,
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Text)
person, err := fmgo.RespondAs[Person](ctx, client, fmgo.Request{
	Prompt: "Generate a fictional person.",
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(person.Name)

Token Counting

CountTokens returns an integer rather than CLI text. A saved transcript can be counted with TokenRequest.Transcript.

count, err := client.CountTokens(ctx, fmgo.TokenRequest{
	Prompt: "Hello world",
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(count)

Availability

availability, err := client.Available(ctx)
if err != nil {
	log.Fatal(err)
}
for _, model := range availability.Models {
	fmt.Println(model.Model, model.Available, model.Reason)
}

Transcripts and Resume

Transcripts are saved and resumed by the native fm functionality.

response, err := client.Respond(ctx, fmgo.Request{
	Prompt:         "Continue the conversation.",
	Resume:         "/tmp/conversation.json",
	SaveTranscript: "/tmp/updated-conversation.json",
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Text)

Interactive Chat

fm chat is an interactive native process, not an undocumented structured chat protocol. StartChat exposes its stdin, stdout, and stderr; consume both output streams, close stdin when finished, then call Wait or Close to reap it.

session, err := client.StartChat(ctx, fmgo.ChatOptions{
	Instructions: "You are a concise assistant.",
})
if err != nil {
	log.Fatal(err)
}
defer session.Close()

Chat Completions Server

Serve manages Apple's native fm serve process. It does not implement an HTTP server or proxy in Go.

server, err := client.Serve(ctx, fmgo.ServerOptions{
	Host: "127.0.0.1",
	Port: 8080,
})
if err != nil {
	log.Fatal(err)
}
defer server.Close()

fmt.Println(server.Addr())

Use Socket instead of Host and Port for a Unix domain socket:

socketServer, err := client.Serve(ctx, fmgo.ServerOptions{
	Socket: "/tmp/fm.sock",
})
if err != nil {
	log.Fatal(err)
}
defer socketServer.Close()

Error Handling

Use errors.Is for expected operational conditions and errors.As for command diagnostics.

client, err := fmgo.New()
if errors.Is(err, fmgo.ErrUnsupportedPlatform) {
	log.Fatal("fmgo requires macOS")
}
if errors.Is(err, fmgo.ErrUnsupportedVersion) {
	log.Fatal("fmgo requires macOS 27 or later")
}
if err != nil {
	log.Fatal(err)
}

response, err := client.Respond(ctx, fmgo.Request{Prompt: "Hello"})
var commandErr *fmgo.CommandError
if errors.As(err, &commandErr) {
	fmt.Println(commandErr.ExitCode, commandErr.Stderr)
}
_ = response

Known CLI conditions also map to ErrFMNotFound, ErrModelUnavailable, and ErrLicenseRequired.

How It Works

Go application
      │
      ▼
    fmgo
      │
      ▼
      fm
      │
      ▼
Apple Foundation Models

fmgo builds argument lists, manages native subprocesses and streaming, translates known errors, and presents the native CLI through Go types.

Platform Scope

fmgo intentionally targets Apple's native fm command on macOS. It does not support Linux, Windows, cloud providers, OpenAI, Anthropic, Ollama, or arbitrary LLM providers.

Disclaimer

fmgo is an independent open-source project and is not affiliated with, endorsed by, or sponsored by Apple Inc. Apple, macOS, and Foundation Models are trademarks of Apple Inc. fmgo does not distribute Apple's fm executable.

License

Licensed under the Apache License 2.0.

Documentation

Overview

Package fmgo provides pure Go access to Apple's Foundation Models CLI.

fmgo invokes the native fm executable. It is not a Swift bridge, does not require cgo, does not access private Apple frameworks, and does not reimplement Foundation Models or provide its own CLI.

fmgo requires macOS 27 or later, the native fm executable, an available Foundation Model, and accepted Foundation Models CLI terms. New validates the platform and returns ErrUnsupportedPlatform or ErrUnsupportedVersion when the machine is incompatible. Operations return ErrFMNotFound when the executable cannot be located.

Create a Client with New, then use Respond or Stream for generation. SchemaFor and RespondAs support structured output. Client also exposes token counting, availability and terms inspection, interactive chat sessions, and lifecycle management for the native fm serve process. All blocking operations accept context.Context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ruanklein/fmgo"
)

func main() {
	client, err := fmgo.New()
	if err != nil {
		return
	}
	response, err := client.Respond(context.Background(), fmgo.Request{
		Prompt:       "Explain goroutines.",
		Instructions: "Be concise.",
	})
	if err != nil {
		return
	}
	fmt.Println(response.Text)
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnsupportedPlatform reports a system other than macOS.
	ErrUnsupportedPlatform = platform.ErrUnsupportedPlatform
	// ErrUnsupportedVersion reports a macOS version earlier than 27.
	ErrUnsupportedVersion = platform.ErrUnsupportedVersion
	// ErrFMNotFound reports that the native fm executable could not be located.
	ErrFMNotFound = errors.New("fm executable not found")
	// ErrModelUnavailable reports that Foundation Models are not ready on this machine.
	ErrModelUnavailable = errors.New("foundation model unavailable")
	// ErrLicenseRequired reports that the Foundation Models CLI terms have not been accepted.
	ErrLicenseRequired = errors.New("foundation models license not accepted")
	// ErrUnexpectedOutput reports output that fmgo cannot safely interpret.
	ErrUnexpectedOutput = errors.New("unexpected fm output")
)

Functions

func RespondAs

func RespondAs[T any](ctx context.Context, client *Client, request Request) (T, error)

RespondAs generates JSON for T and decodes it into T.

Types

type Availability

type Availability struct {
	Models []ModelAvailability
}

Availability contains statuses reported by fm available.

type ChatOptions

type ChatOptions struct {
	Model           Model
	Instructions    string
	Resume          string
	Continue        bool
	SetDefaultModel Model
	Tools           []Tool
}

ChatOptions configures an interactive fm chat session.

type ChatSession

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

ChatSession owns the standard streams and process for an interactive chat.

func (*ChatSession) Close

func (s *ChatSession) Close() error

Close terminates and reaps the chat process. It is safe to call more than once.

func (*ChatSession) Stderr

func (s *ChatSession) Stderr() io.ReadCloser

Stderr returns the chat process error stream.

func (*ChatSession) Stdin

func (s *ChatSession) Stdin() io.WriteCloser

Stdin returns the chat process input stream.

func (*ChatSession) Stdout

func (s *ChatSession) Stdout() io.ReadCloser

Stdout returns the chat process output stream.

func (*ChatSession) Wait

func (s *ChatSession) Wait() error

Wait waits for the chat process after its output streams have been consumed.

type Client

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

Client invokes the native fm executable.

func New

func New(options ...Option) (*Client, error)

New creates a Client that uses fm from PATH by default.

func (*Client) Available

func (c *Client) Available(ctx context.Context) (Availability, error)

Available reports Foundation Models availability.

func (*Client) CountTokens

func (c *Client) CountTokens(ctx context.Context, request TokenRequest) (int, error)

CountTokens counts Foundation Models tokens.

func (*Client) GenerateSchema

func (c *Client) GenerateSchema(ctx context.Context, object ObjectSchema) (Schema, error)

GenerateSchema creates a schema through fm schema object.

func (*Client) License

func (c *Client) License(ctx context.Context) (LicenseStatus, error)

License reports whether the Foundation Models CLI terms were accepted.

func (*Client) Respond

func (c *Client) Respond(ctx context.Context, request Request) (Response, error)

Respond generates a complete non-streaming response.

func (*Client) Serve

func (c *Client) Serve(ctx context.Context, options ServerOptions) (*Server, error)

Serve starts fm serve and waits until its configured address accepts connections.

func (*Client) ShowLicense

func (c *Client) ShowLicense(ctx context.Context) (string, error)

ShowLicense returns the Foundation Models CLI terms without prompting.

func (*Client) StartChat

func (c *Client) StartChat(ctx context.Context, options ChatOptions) (*ChatSession, error)

StartChat starts an interactive chat session.

func (*Client) Stream

func (c *Client) Stream(ctx context.Context, request Request) (*Stream, error)

Stream starts a streaming response.

type CommandError

type CommandError struct {
	Command  string
	ExitCode int
	Stderr   string
	// contains filtered or unexported fields
}

CommandError describes a failed native fm invocation.

func (*CommandError) Error

func (e *CommandError) Error() string

func (*CommandError) Unwrap

func (e *CommandError) Unwrap() error

Unwrap returns a recognized operational cause when one is available.

type GuardrailLevel

type GuardrailLevel string

GuardrailLevel configures Apple's supported guardrail level.

const (
	GuardrailDefault                          GuardrailLevel = "default"
	GuardrailPermissiveContentTransformations GuardrailLevel = "permissive-content-transformations"
)

type LicenseStatus

type LicenseStatus struct {
	Accepted bool
}

LicenseStatus reports whether the local machine accepted the fm terms.

type Model

type Model string

Model identifies a Foundation Model exposed by fm.

const (
	// ModelSystem is the on-device Apple Foundation Model.
	ModelSystem Model = "system"
)

type ModelAvailability

type ModelAvailability struct {
	Model     Model
	Available bool
	Reason    string
}

ModelAvailability describes whether a model is ready to use.

type ObjectSchema

type ObjectSchema struct {
	Name       string
	Properties []SchemaProperty
}

ObjectSchema describes an object passed to fm schema object.

type Option

type Option func(*Client)

Option configures a Client.

func WithExecutable

func WithExecutable(path string) Option

WithExecutable overrides the fm executable path.

type Request

type Request struct {
	Prompt         string
	Instructions   string
	Model          Model
	Text           []string
	Images         []string
	ImageLabels    []string
	Tools          []Tool
	Schema         Schema
	SchemaFile     string
	Resume         string
	SaveTranscript string
	Greedy         bool
	Verbose        bool
	UseCase        UseCase
	Guardrails     GuardrailLevel
}

Request configures a Foundation Models response.

type Response

type Response struct {
	Text string
}

Response is a non-streaming Foundation Models response.

type Schema

type Schema json.RawMessage

Schema is inline JSON Schema accepted by fm respond.

func ParseSchema

func ParseSchema(data []byte) (Schema, error)

ParseSchema validates inline JSON Schema.

func SchemaFor

func SchemaFor[T any]() (Schema, error)

SchemaFor generates JSON Schema for T without invoking fm.

Example
package main

import (
	"github.com/ruanklein/fmgo"
)

func main() {
	type Person struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}

	schema, err := fmgo.SchemaFor[Person]()
	if err != nil {
		return
	}
	_ = schema
}

func SchemaFrom

func SchemaFrom(value any) (Schema, error)

SchemaFrom generates JSON Schema for a Go value's type.

type SchemaProperty

type SchemaProperty struct {
	Name        string
	Type        SchemaType
	Description string
	Optional    bool
	Array       bool
	Schema      Schema
	Choices     []Schema
}

SchemaProperty describes a native fm schema property.

type SchemaType

type SchemaType string

SchemaType identifies a property declaration accepted by fm schema object.

const (
	SchemaString  SchemaType = "string"
	SchemaInteger SchemaType = "integer"
	SchemaDouble  SchemaType = "double"
	SchemaBoolean SchemaType = "boolean"
	SchemaObject  SchemaType = "object"
	SchemaAnyOf   SchemaType = "anyOf"
)

type Server

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

Server manages a native fm serve process.

func (*Server) Addr

func (s *Server) Addr() string

Addr returns the server's configured network address.

func (*Server) Close

func (s *Server) Close() error

Close terminates and reaps the native server. It is safe to call more than once.

func (*Server) Wait

func (s *Server) Wait() error

Wait waits for the native server to exit.

type ServerOptions

type ServerOptions struct {
	Host           string
	Port           int
	Socket         string
	StartupTimeout time.Duration
}

ServerOptions configures the native fm serve process.

type Stream

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

Stream incrementally reads a response from fm. Text returns the latest read block.

func (*Stream) Close

func (s *Stream) Close() error

Close terminates the response process. It is safe to call more than once.

func (*Stream) Err

func (s *Stream) Err() error

Err returns the terminal stream error, if any.

func (*Stream) Next

func (s *Stream) Next() bool

Next reads the next available output block.

func (*Stream) Text

func (s *Stream) Text() string

Text returns the text read by the most recent successful Next call.

type TokenRequest

type TokenRequest struct {
	Prompt       string
	Instructions string
	Text         []string
	Images       []string
	Transcript   string
}

TokenRequest describes content to count with fm count-tokens.

type Tool

type Tool string

Tool identifies a native fm tool.

const (
	ToolBarcode Tool = "barcode"
	ToolOCR     Tool = "ocr"
)

type UseCase

type UseCase string

UseCase configures the system model's intended task.

const (
	UseCaseGeneral        UseCase = "general"
	UseCaseContentTagging UseCase = "content-tagging"
)

Directories

Path Synopsis
cmd
respond command
server command
stream command
structured command
internal

Jump to

Keyboard shortcuts

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