Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Build output
/build/
/bin/

# Local development
.claude/
agent/
charon
16 changes: 5 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
BINARY := charon
CMD := ./cmd/charon
BUILD_DIR := ./build
BIN_DIR := ./bin
IMAGE_NAME ?= charon
IMAGE_TAG ?= latest

GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)

OUT := $(BUILD_DIR)/$(GOOS)/$(GOARCH)/$(BINARY)

OPENRESPONSES_DIR ?= ../openresponses

.PHONY: all fmt fmt-check vet lint presubmit test test-unit test-integration test-compliance test-disruptive test-system test-compliance-bun tidy update build image clean
Expand Down Expand Up @@ -86,11 +79,12 @@ update:
# ── Build & package ───────────────────────────────────────────────────────────

build:
mkdir -p $(BUILD_DIR)/$(GOOS)/$(GOARCH)
go build -o $(OUT) $(CMD)
mkdir -p $(BIN_DIR)
go build -o $(BIN_DIR)/charon ./cmd/charon
go build -o $(BIN_DIR)/proxy ./cmd/proxy

image:
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) .

clean:
rm -rf $(BUILD_DIR)
rm -rf $(BIN_DIR)
7 changes: 3 additions & 4 deletions cmd/charon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ import (

"github.com/elevran/charon/internal/chainstore"
pebblebe "github.com/elevran/charon/internal/chainstore/pebble"
"github.com/elevran/charon/internal/config"
"github.com/elevran/charon/internal/metrics"
"github.com/elevran/charon/internal/charonconfig"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/internal/telemetry"
)
Expand All @@ -29,7 +28,7 @@ func main() {
func run() error {
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))

opts := config.NewCharonOptions()
opts := charonconfig.NewOptions()
fs := flag.NewFlagSet("charon", flag.ExitOnError)
opts.AddFlags(fs)
_ = fs.Parse(os.Args[1:])
Expand All @@ -43,7 +42,7 @@ func run() error {
}

reg := prometheus.NewRegistry()
if err := metrics.Register(reg, ""); err != nil {
if err := server.RegisterMetrics(reg, ""); err != nil {
log.Error("register metrics", "err", err)
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/charon/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (

"github.com/stretchr/testify/require"

"github.com/elevran/charon/internal/config"
"github.com/elevran/charon/internal/charonconfig"
)

func TestConfigDefaults(t *testing.T) {
opts := config.NewCharonOptions()
opts := charonconfig.NewOptions()
require.Equal(t, ":8081", opts.Listen)
require.Equal(t, 30, opts.TTLDays)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxy/assemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"time"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
)

// marshalStoredResponse serialises an inference response and request metadata
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxy/assemble_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"testing"
"time"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
)

// helper: decode json.RawMessage value for a key from map.
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxy/disruptive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
)

// failingCharonMux returns an http.Handler that returns 507 for all PUT
Expand Down
34 changes: 17 additions & 17 deletions cmd/proxy/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"net/http"
"time"

"github.com/elevran/charon/internal/httputil"
"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/pkg/charon"
)

Expand Down Expand Up @@ -46,23 +46,23 @@ func RegisterHandlers(mux *http.ServeMux, h *Handler) {
func (h *Handler) HandleCreate(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
httputil.WriteError(w, http.StatusBadRequest, "failed to read request body")
server.WriteError(w, http.StatusBadRequest, "failed to read request body")
return
}

var req CreateRequest
if err := json.Unmarshal(bodyBytes, &req); err != nil {
httputil.WriteError(w, http.StatusBadRequest, "invalid request body")
server.WriteError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Model == "" {
httputil.WriteError(w, http.StatusBadRequest, "model is required")
server.WriteError(w, http.StatusBadRequest, "model is required")
return
}

var rawReq map[string]json.RawMessage
if err := json.Unmarshal(bodyBytes, &rawReq); err != nil {
httputil.WriteError(w, http.StatusBadRequest, "invalid request body")
server.WriteError(w, http.StatusBadRequest, "invalid request body")
return
}

Expand All @@ -76,7 +76,7 @@ func (h *Handler) HandleCreate(w http.ResponseWriter, r *http.Request) {

inputItems, err := inputToItems(req.Input)
if err != nil {
httputil.WriteError(w, http.StatusBadRequest, "invalid input")
server.WriteError(w, http.StatusBadRequest, "invalid input")
return
}

Expand All @@ -101,7 +101,7 @@ func (h *Handler) HandleCreate(w http.ResponseWriter, r *http.Request) {
infResp, err := h.inf.Complete(ctx, infMap)
if err != nil {
h.log.Error("inference complete", "err", err)
httputil.WriteError(w, http.StatusBadGateway, "inference error")
server.WriteError(w, http.StatusBadGateway, "inference error")
return
}
completedAt := time.Now()
Expand All @@ -110,13 +110,13 @@ func (h *Handler) HandleCreate(w http.ResponseWriter, r *http.Request) {
responseBlob := marshalStoredResponse(infResp, req.PreviousResponseID, req.Instructions, req.Background)
if err := h.charon.Store(ctx, infResp.ID, stagingID, tenantKey, responseBlob); err != nil {
h.log.Error("charon store", "id", infResp.ID, "err", err)
httputil.WriteError(w, http.StatusInternalServerError, "storage error")
server.WriteError(w, http.StatusInternalServerError, "storage error")
return
}
}

resource := buildResponseResource(infResp, req.PreviousResponseID, req.ShouldStore(), req.Background, createdAt, &completedAt)
httputil.WriteJSON(w, http.StatusOK, resource)
server.WriteJSON(w, http.StatusOK, resource)
}

// HandleRetrieve handles GET /responses/{id}.
Expand All @@ -133,7 +133,7 @@ func (h *Handler) HandleRetrieve(w http.ResponseWriter, r *http.Request) {
var stored storedResponse
if err := json.Unmarshal(retrieved.ResponseBlob, &stored); err != nil {
h.log.Error("unmarshal stored response", "id", id, "err", err)
httputil.WriteError(w, http.StatusInternalServerError, "internal server error")
server.WriteError(w, http.StatusInternalServerError, "internal server error")
return
}

Expand All @@ -160,7 +160,7 @@ func (h *Handler) HandleRetrieve(w http.ResponseWriter, r *http.Request) {
Metadata: map[string]string{},
ServiceTier: "default",
}
httputil.WriteJSON(w, http.StatusOK, resource)
server.WriteJSON(w, http.StatusOK, resource)
}

// HandleDelete handles DELETE /responses/{id}.
Expand All @@ -181,10 +181,10 @@ func (h *Handler) HandleCompact(w http.ResponseWriter, r *http.Request) {
}
_ = json.NewDecoder(r.Body).Decode(&body)
if body.Model == "" {
httputil.WriteError(w, http.StatusBadRequest, "model is required")
server.WriteError(w, http.StatusBadRequest, "model is required")
return
}
httputil.WriteError(w, http.StatusNotImplemented, "compact not implemented")
server.WriteError(w, http.StatusNotImplemented, "compact not implemented")
}

// HandleListOrWS is implemented in ws.go.
Expand All @@ -196,17 +196,17 @@ func (h *Handler) HandleCompact(w http.ResponseWriter, r *http.Request) {
func (h *Handler) mapCharonError(w http.ResponseWriter, err error, notFoundCode string) {
switch {
case errors.Is(err, charon.ErrNotFound):
httputil.WriteJSON(w, http.StatusNotFound, map[string]string{
server.WriteJSON(w, http.StatusNotFound, map[string]string{
"code": notFoundCode,
"message": err.Error(),
})
case errors.Is(err, charon.ErrChainCorrupted):
httputil.WriteJSON(w, http.StatusConflict, map[string]string{
server.WriteJSON(w, http.StatusConflict, map[string]string{
"code": "chain_corrupted",
"message": err.Error(),
})
default:
h.log.Error("charon error", "err", err)
httputil.WriteError(w, http.StatusInternalServerError, "internal server error")
server.WriteError(w, http.StatusInternalServerError, "internal server error")
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
"testing"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
)

var ctx = context.Background()
Expand Down
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions cmd/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"syscall"
"time"

"github.com/elevran/charon/internal/config"
"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
"github.com/elevran/charon/internal/proxyconfig"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/internal/telemetry"
"github.com/elevran/charon/pkg/charon"
Expand All @@ -26,7 +26,7 @@ func main() {
func run() error {
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))

opts := config.NewProxyOptions()
opts := proxyconfig.NewOptions()
fs := flag.NewFlagSet("proxy", flag.ExitOnError)
opts.AddFlags(fs)
_ = fs.Parse(os.Args[1:])
Expand Down
8 changes: 4 additions & 4 deletions cmd/proxy/sse.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"strconv"
"time"

"github.com/elevran/charon/internal/httputil"
"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/pkg/charon"
)

Expand Down Expand Up @@ -64,7 +64,7 @@ func (h *Handler) handleStream(w http.ResponseWriter, r *http.Request, req Creat

inputItems, err := inputToItems(req.Input)
if err != nil {
httputil.WriteError(w, http.StatusBadRequest, "invalid input")
server.WriteError(w, http.StatusBadRequest, "invalid input")
return
}

Expand All @@ -90,7 +90,7 @@ func (h *Handler) handleStream(w http.ResponseWriter, r *http.Request, req Creat
ch, err := h.inf.Stream(ctx, infMap)
if err != nil {
h.log.Error("inference stream", "err", err)
httputil.WriteError(w, http.StatusBadGateway, "inference error")
server.WriteError(w, http.StatusBadGateway, "inference error")
return
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/proxy/stateless_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
)

// capturedInfReq records a single request body received by the inference server.
Expand Down
2 changes: 1 addition & 1 deletion cmd/proxy/testhelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"

"github.com/elevran/charon/cmd/proxy/inference"
"github.com/elevran/charon/internal/chainstore"
pebblebe "github.com/elevran/charon/internal/chainstore/pebble"
"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/pkg/charon"
)
Expand Down
7 changes: 3 additions & 4 deletions cmd/proxy/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import (
"sync"
"time"

"github.com/elevran/charon/internal/httputil"

"github.com/gorilla/websocket"

"github.com/elevran/charon/internal/inference"
"github.com/elevran/charon/cmd/proxy/inference"
"github.com/elevran/charon/internal/server"
"github.com/elevran/charon/pkg/charon"
)

Expand Down Expand Up @@ -78,7 +77,7 @@ func (h *Handler) HandleListOrWS(w http.ResponseWriter, r *http.Request) {
h.HandleWebSocket(w, r)
return
}
httputil.WriteJSON(w, http.StatusOK, map[string]interface{}{
server.WriteJSON(w, http.StatusOK, map[string]interface{}{
"object": "list",
"data": []interface{}{},
"has_more": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package config
package charonconfig

import (
"encoding/json"
Expand All @@ -19,14 +19,12 @@ var unitMultipliers = map[string]int64{
}

func (b *ByteSize) UnmarshalJSON(data []byte) error {
// Try plain number first.
var n int64
if err := json.Unmarshal(data, &n); err == nil {
*b = ByteSize(n)
return nil
}

// Try quoted string.
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("bytesize: expected number or string, got %s", data)
Expand All @@ -35,7 +33,6 @@ func (b *ByteSize) UnmarshalJSON(data []byte) error {
s = strings.TrimSpace(s)
lower := strings.ToLower(s)

// Find where the digits end.
i := 0
for i < len(s) && (s[i] == '-' || s[i] == '+' || (s[i] >= '0' && s[i] <= '9')) {
i++
Expand Down
Loading
Loading