Skip to content
Open
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
2 changes: 1 addition & 1 deletion common/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/curioswitch/cookchat/common

go 1.25.0

require google.golang.org/genai v1.24.0
require google.golang.org/genai v1.25.0

require (
cloud.google.com/go v0.122.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions common/go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genai v1.24.0 h1:j5lt+Qr7W0+OBxwwEPe4DQ+ygEqpvZuSBvYoHIuUjhg=
google.golang.org/genai v1.24.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE=
google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
Expand Down
2 changes: 1 addition & 1 deletion crawler/server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/curioswitch/go-curiostack v0.0.0-20250909080149-d127817587e7
github.com/gocolly/colly/v2 v2.2.0
github.com/wandb/parallel v0.2.2
google.golang.org/genai v1.24.0
google.golang.org/genai v1.25.0
google.golang.org/grpc v1.75.1
)

Expand Down
4 changes: 2 additions & 2 deletions crawler/server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genai v1.24.0 h1:j5lt+Qr7W0+OBxwwEPe4DQ+ygEqpvZuSBvYoHIuUjhg=
google.golang.org/genai v1.24.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE=
google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
Expand Down
Binary file removed frontend/api/build/descriptors/descriptorset.pb
Binary file not shown.
7 changes: 6 additions & 1 deletion frontend/api/build/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ go 1.25.0
require (
github.com/curioswitch/go-build v0.6.1
github.com/curioswitch/go-curiostack v0.0.0-20250909080149-d127817587e7
github.com/curioswitch/go-docs-handler v0.1.5
github.com/curioswitch/go-docs-handler/plugins/proto v0.1.5
github.com/goyek/goyek/v2 v2.3.0
github.com/goyek/x v0.3.0
google.golang.org/protobuf v1.36.9
)

require (
github.com/fatih/color v1.18.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goyek/goyek/v2 v2.3.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/parsers/yaml v1.1.0 // indirect
github.com/knadh/koanf/providers/env/v2 v2.0.0 // indirect
github.com/knadh/koanf/providers/rawbytes v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.2.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
Expand Down
10 changes: 10 additions & 0 deletions frontend/api/build/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ github.com/curioswitch/go-build v0.6.1 h1:6rIt0hQkWVoVARaXjOEwAU/07wTEA0KkHglscA
github.com/curioswitch/go-build v0.6.1/go.mod h1:6YV8tVSmfwamIWTkmKkMUzgoI+SnZNOUQI2CXFs5Rv8=
github.com/curioswitch/go-curiostack v0.0.0-20250909080149-d127817587e7 h1:No3mNL0Hcb4CxDT9hcsntqYqJIoSOMfVPhfiSmQ/uFA=
github.com/curioswitch/go-curiostack v0.0.0-20250909080149-d127817587e7/go.mod h1:+334ClkBgQj5vbKd+9qtGAbEhOPPRvor2l8v0ZMtjhM=
github.com/curioswitch/go-docs-handler v0.1.5 h1:MIdGAlcpiQLi1grnPwc/CVBuSOu374b7S3W2GKU0oiU=
github.com/curioswitch/go-docs-handler v0.1.5/go.mod h1:gSxE5Z46knEcLh6EtrwlmsApqcAp2bQUJl3csQiePWI=
github.com/curioswitch/go-docs-handler/plugins/proto v0.1.5 h1:OjIICuxWhb+3+0GJ/wsbwk4H/596lXucVBO7lcQoEh8=
github.com/curioswitch/go-docs-handler/plugins/proto v0.1.5/go.mod h1:Cka/I8hexDKVFwu/T01m44w5TPrCDu/ip1bTkOFEJmk=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/goyek/goyek/v2 v2.3.0 h1:ZQlQZp+JOkbZXuPNw6SbeQxYMRKB0Gm3nbC2K9IY1r4=
github.com/goyek/goyek/v2 v2.3.0/go.mod h1:VKzmleKfV5LB9f20Xg5umuPC4XfSdDXwPNesfr1RsLU=
github.com/goyek/x v0.3.0 h1:JEgcLmqnxAPFR7/IqPMBoSRUcvykNS3o7YCVrC1VKHQ=
Expand All @@ -30,6 +36,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
Expand All @@ -53,6 +61,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
230 changes: 230 additions & 0 deletions frontend/api/build/jsonschema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// Copyright (c) CurioSwitch (choko@curioswitch.org)
// SPDX-License-Identifier: BUSL-1.1

package main

import (
"encoding/json"
"strings"

"github.com/curioswitch/go-docs-handler/specification"
)

// Modified go-docs-handler jsonschema generation to generate for types and
// remove $refs.

type additionalPropertiesSchema struct {
field jsonSchemaField
}

func (s additionalPropertiesSchema) MarshalJSON() ([]byte, error) {
return json.Marshal(s.field)
}

type additionalPropertiesBool struct {
value bool
}

func (s additionalPropertiesBool) MarshalJSON() ([]byte, error) {
return json.Marshal(s.value)
}

type jsonSchemaField struct {
Description string `json:"description"`
Type string `json:"type,omitempty"`
Enum []string `json:"enum,omitempty"`
Properties map[string]jsonSchemaField `json:"properties,omitempty"`
AdditionalProperties json.Marshaler `json:"additionalProperties,omitempty"`
Items *jsonSchemaField `json:"items,omitempty"`
}

type jsonSchema struct {
ID string `json:"-"`
Title string `json:"title"`
Description string `json:"description"`
Properties map[string]jsonSchemaField `json:"properties"`
AdditionalProperties bool `json:"additionalProperties"`
Type string `json:"type"`
}

func generateJSONSchema(spec *specification.Specification) []jsonSchema {
g := &jSONSchemaGenerator{
typeNameToEnum: map[string]specification.Enum{},
typeNameToStruct: map[string]specification.Struct{},
}

for _, enum := range spec.Enums {
g.typeNameToEnum[enum.Name] = enum
}
for _, s := range spec.Structs {
g.typeNameToStruct[s.Name] = s
}

return g.generate(spec)
}

type jSONSchemaGenerator struct {
typeNameToEnum map[string]specification.Enum
typeNameToStruct map[string]specification.Struct
}

func (g *jSONSchemaGenerator) generate(spec *specification.Specification) []jsonSchema {
schemas := make([]jsonSchema, len(spec.Structs))
for i, s := range spec.Structs {
schemas[i] = g.generateStructSchema(s)
}
return schemas
}

func (g *jSONSchemaGenerator) generateStructSchema(s specification.Struct) jsonSchema {
schema := jsonSchema{
ID: s.Name,
Description: strings.TrimSpace(s.DescriptionInfo.DocString),
AdditionalProperties: false,
Type: "object",
}

schema.Properties = g.generateProperties(s.Fields, map[string]string{}, "#")

return schema
}

func (g *jSONSchemaGenerator) generateField(field specification.Field, visited map[string]string, path string) jsonSchemaField {
schema := jsonSchemaField{
Description: strings.TrimSpace(field.DescriptionInfo.DocString),
}

schema.Type = getSchemaType(field.TypeSignature)
if field.TypeSignature.Type() == specification.TypeSignatureTypeEnum {
schema.Enum = g.getEnumType(field.TypeSignature)
}

currentPath := path
if len(field.Name) > 0 {
currentPath += "/" + field.Name
}

// We can only have references to struct types, not primitives
switch schema.Type {
case "array", "object":
visited[field.TypeSignature.Signature()] = currentPath
}

switch {
case field.TypeSignature.Type() == specification.TypeSignatureTypeMap:
aProps := g.generateMapAdditionalProperties(field, visited, currentPath)
schema.AdditionalProperties = additionalPropertiesSchema{field: aProps}
case field.TypeSignature.Type() == specification.TypeSignatureTypeIterable:
items := g.generateArrayItems(field, visited, currentPath)
schema.Items = &items
case schema.Type == "object":
props, found := g.generateStructProperties(field, visited, currentPath)
if found {
schema.AdditionalProperties = additionalPropertiesBool{value: false}
schema.Properties = props
} else {
// When we have a struct but the definition cannot not be found, we go ahead
// and allow all additional properties since it may still be more useful than
// being completely unusable.
schema.AdditionalProperties = additionalPropertiesBool{value: true}
}
}

return schema
}

func (g *jSONSchemaGenerator) generateProperties(fields []specification.Field, visited map[string]string, path string) map[string]jsonSchemaField {
properties := make(map[string]jsonSchemaField)

for _, field := range fields {
switch field.Location {
case specification.FieldLocationBody, specification.FieldLocationUnspecified:
properties[field.Name] = g.generateField(field, visited, path+"/properties")
}
}

return properties
}

func (g *jSONSchemaGenerator) generateMapAdditionalProperties(field specification.Field, visited map[string]string, path string) jsonSchemaField {
type mt interface {
KeyType() specification.TypeSignature
ValueType() specification.TypeSignature
}

valueType := field.TypeSignature.(mt).ValueType()
valueFieldInfo := specification.Field{
Location: specification.FieldLocationBody,
TypeSignature: valueType,
}

return g.generateField(valueFieldInfo, visited, path+"/additionalProperties")
}

func (g *jSONSchemaGenerator) generateArrayItems(field specification.Field, visited map[string]string, path string) jsonSchemaField {
type at interface {
ItemType() specification.TypeSignature
}

itemType := field.TypeSignature.(at).ItemType()
itemField := specification.Field{
Location: specification.FieldLocationBody,
TypeSignature: itemType,
}

return g.generateField(itemField, visited, path+"/items")
}

func (g *jSONSchemaGenerator) generateStructProperties(
field specification.Field, visited map[string]string, path string,
) (map[string]jsonSchemaField, bool) {
if s, ok := g.typeNameToStruct[field.TypeSignature.Signature()]; ok {
return g.generateProperties(s.Fields, visited, path), true
}
return map[string]jsonSchemaField{}, false
}

func (g *jSONSchemaGenerator) getEnumType(t specification.TypeSignature) []string {
var res []string

if e, ok := g.typeNameToEnum[t.Signature()]; ok {
for _, value := range e.Values {
res = append(res, value.Name)
}
}

return res
}

func getSchemaType(t specification.TypeSignature) string {
switch t.Type() {
case specification.TypeSignatureTypeEnum:
return "string"
case specification.TypeSignatureTypeIterable:
return "array"
case specification.TypeSignatureTypeMap:
return "object"
case specification.TypeSignatureTypeBase:
return getBaseType(t.Signature())
default:
return "object"
}
}

func getBaseType(signature string) string {
switch signature {
case "bool", "boolean":
return "boolean"
case "short", "number", "float", "double":
return "number"
case "i", "i8", "i16", "i32", "i64", "integer", "int",
"l32", "l64", "long", "long32", "long64", "int32", "int64",
"uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64",
"sfixed32", "sfixed64":
return "integer"
case "binary", "byte", "bytes", "string":
return "string"
default:
return "object"
}
}
Loading
Loading