package cfg
import (
"log/slog"
"strings"
)
var Version = "0.5.3"
var (
FlagLogLevel string
// MaxLineLen is the maximum line length for code generation.
// Prompt bodies are not wrapped.
MaxLineLen int
)
func LogLevel() slog.Level {
switch strings.ToUpper(FlagLogLevel) {
case "DEBUG":
return slog.LevelDebug
case "INFO":
return slog.LevelInfo
case "WARN":
return slog.LevelWarn
case "ERROR":
return slog.LevelError
default:
return slog.LevelInfo
}
}
func TestMode() {
MaxLineLen = 80
FlagLogLevel = "DEBUG"
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gen
import (
"github.com/omniaura/agentflow/cfg"
"github.com/omniaura/agentflow/cmd/af/gen/prompts"
"github.com/spf13/cobra"
)
func CMD() *cobra.Command {
cmd := &cobra.Command{
Use: "gen",
Short: "Generate code from .af files",
}
cmd.PersistentFlags().IntVar(&cfg.MaxLineLen, "max-line-len", 80, "Maximum line length")
cmd.AddCommand(prompts.CMD())
return cmd
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package prompts
import (
"io"
"io/fs"
"log/slog"
"os"
"path/filepath"
"github.com/omniaura/agentflow/pkg/assert"
"github.com/omniaura/agentflow/pkg/ast"
"github.com/omniaura/agentflow/pkg/gen/gogen"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
var (
Dir string
)
func flags(cmd *cobra.Command) *cobra.Command {
cmd.Flags().StringVarP(&Dir,
"dir", "d", ".", "Directory to read .af files from. Defaults to current directory.")
return cmd
}
func CMD() *cobra.Command {
cmd := &cobra.Command{
Use: "prompts",
Short: "Generate prompts",
Long: `Generate prompts from .af files in the input directory.
The generated prompts will be written next to their corresponding .af files.`,
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
// Use filepath.Walk to recursively find all .af files
var files []string
err := filepath.WalkDir(Dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() && filepath.Ext(path) == ".af" {
files = append(files, path)
}
return nil
})
assert.NoError(err)
group, _ := errgroup.WithContext(ctx)
for _, name := range files {
group.Go(func() error {
file, err := os.Open(name)
if err != nil {
return err
}
defer file.Close()
f, err := io.ReadAll(file)
if err != nil {
return err
}
fName := filepath.Base(file.Name())
ff, err := ast.NewFile(fName, f)
if err != nil {
return err
}
// Place the output file in the same directory as the .af file, with _af.go suffix
outFileName := ff.Name + "_af.go"
outFilePath := filepath.Join(filepath.Dir(name), outFileName)
// Compute package name from directory.
// When .af files are in the root scan directory, filepath.Base(".")
// returns "." which is not a valid Go package name. Fall back to
// $GOPACKAGE which go generate sets automatically.
dirName := filepath.Base(filepath.Dir(name))
if dirName == "." {
if pkg := os.Getenv("GOPACKAGE"); pkg != "" {
dirName = pkg
}
}
outFile, err := os.OpenFile(outFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer outFile.Close()
err = gogen.GenFile(outFile, ff, dirName)
if err != nil {
return err
}
slog.Info("Generated", "file", outFilePath)
return nil
})
}
if err := group.Wait(); err != nil {
slog.Error("Error", "error", err)
os.Exit(1)
}
},
}
return flags(cmd)
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lsp
import (
"fmt"
"log/slog"
"os"
"os/signal"
"runtime"
"runtime/debug"
"syscall"
"time"
"github.com/omniaura/agentflow/pkg/lsp"
"github.com/spf13/cobra"
)
var (
// Mode controls how the LSP server communicates
// "stdio" uses stdin/stdout, "tcp" uses TCP on specified port
Mode string
// Port is the TCP port to listen on when mode is "tcp"
Port int
// Debug enables debug logging
Debug bool
)
// CMD creates the lsp command
func CMD() *cobra.Command {
cmd := &cobra.Command{
Use: "lsp",
Short: "Start the AgentFlow Language Server Protocol (LSP) server",
Long: `Start the AgentFlow LSP server to provide language support for .af files.
The LSP server provides:
- Syntax highlighting
- Error diagnostics
- Auto-completion
- Go-to-definition
- Hover information
- Document outline`,
Run: func(cmd *cobra.Command, args []string) {
start := time.Now()
// Set up panic recovery
defer func() {
if r := recover(); r != nil {
slog.Error("LSP server panic recovered",
"panic", r,
"stack", string(debug.Stack()),
"uptime", time.Since(start))
os.Exit(1)
}
}()
// Configure logging level based on debug flag
if Debug {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})))
slog.Info("Debug logging enabled")
} else {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})))
}
// Set up signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
sig := <-sigChan
slog.Info("Received signal, shutting down gracefully",
"signal", sig.String(),
"uptime", time.Since(start))
os.Exit(0)
}()
slog.Info("Starting AgentFlow LSP server",
"mode", Mode,
"port", Port,
"debug", Debug,
"pid", os.Getpid(),
"goVersion", runtime.Version(),
"numCPU", runtime.NumCPU())
if Debug {
slog.Debug("Command arguments", "args", args)
slog.Debug("Environment details",
"GOOS", runtime.GOOS,
"GOARCH", runtime.GOARCH,
"workingDir", getWorkingDir())
// Log memory stats
var m runtime.MemStats
runtime.ReadMemStats(&m)
slog.Debug("Initial memory stats",
"allocMB", bToMb(m.Alloc),
"totalAllocMB", bToMb(m.TotalAlloc),
"sysMB", bToMb(m.Sys),
"numGC", m.NumGC)
}
server := lsp.NewServer(Debug)
serverCreateDuration := time.Since(start)
slog.Info("LSP server instance created", "duration", serverCreateDuration)
switch Mode {
case "stdio":
slog.Info("Starting AgentFlow LSP server", "mode", "stdio")
if Debug {
slog.Debug("Server will communicate via stdin/stdout")
}
runStart := time.Now()
// Wrap the server.RunStdio call with additional error context
func() {
defer func() {
if r := recover(); r != nil {
slog.Error("Panic in RunStdio",
"panic", r,
"stack", string(debug.Stack()),
"mode", "stdio",
"uptime", time.Since(start),
"runDuration", time.Since(runStart))
os.Exit(1)
}
}()
slog.Info("About to call server.RunStdio()")
err := server.RunStdio()
slog.Info("server.RunStdio() returned",
"error", err,
"runDuration", time.Since(runStart))
if err != nil {
slog.Error("LSP server error",
"mode", "stdio",
"error", err,
"errorType", fmt.Sprintf("%T", err),
"uptime", time.Since(start),
"runDuration", time.Since(runStart))
os.Exit(1)
}
}()
case "tcp":
address := fmt.Sprintf(":%d", Port)
slog.Info("Starting AgentFlow LSP server",
"mode", "tcp",
"port", Port,
"address", address)
if Debug {
slog.Debug("Server will listen on TCP",
"address", address,
"port", Port)
}
runStart := time.Now()
// Wrap the server.RunTCP call with additional error context
func() {
defer func() {
if r := recover(); r != nil {
slog.Error("Panic in RunTCP",
"panic", r,
"stack", string(debug.Stack()),
"mode", "tcp",
"address", address,
"uptime", time.Since(start),
"runDuration", time.Since(runStart))
os.Exit(1)
}
}()
slog.Info("About to call server.RunTCP()", "address", address)
err := server.RunTCP(address)
slog.Info("server.RunTCP() returned",
"error", err,
"address", address,
"runDuration", time.Since(runStart))
if err != nil {
slog.Error("LSP server error",
"mode", "tcp",
"address", address,
"error", err,
"errorType", fmt.Sprintf("%T", err),
"uptime", time.Since(start),
"runDuration", time.Since(runStart))
os.Exit(1)
}
}()
default:
slog.Error("Invalid mode",
"mode", Mode,
"validModes", []string{"stdio", "tcp"})
fmt.Fprintf(os.Stderr, "Invalid mode: %s. Use 'stdio' or 'tcp'\n", Mode)
os.Exit(1)
}
// This should never be reached for stdio mode, but for TCP mode
// if the server shuts down gracefully
totalUptime := time.Since(start)
slog.Info("LSP server shutdown complete",
"mode", Mode,
"totalUptime", totalUptime)
},
}
cmd.Flags().StringVar(&Mode, "mode", "stdio", "Communication mode: 'stdio' or 'tcp'")
cmd.Flags().IntVar(&Port, "port", 4389, "TCP port to listen on (when mode is 'tcp')")
cmd.Flags().BoolVar(&Debug, "debug", false, "Enable debug logging")
return cmd
}
// getWorkingDir safely gets the current working directory
func getWorkingDir() string {
if wd, err := os.Getwd(); err == nil {
return wd
}
return "unknown"
}
// bToMb converts bytes to megabytes
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"github.com/omniaura/agentflow/cfg"
"github.com/omniaura/agentflow/cmd/af/gen"
"github.com/omniaura/agentflow/cmd/af/lsp"
"github.com/omniaura/agentflow/pkg/assert"
"github.com/omniaura/agentflow/pkg/logger"
"github.com/spf13/cobra"
)
var Root = &cobra.Command{
Version: cfg.Version, // This line will be updated by the sync-version script
Use: "af",
Short: "AgentFlow CLI",
Long: "AgentFlow is a CLI for bootstrapping AI agents.",
PersistentPreRun: func(cmd *cobra.Command, args []string) { logger.Setup() },
}
func main() {
Root.PersistentFlags().StringVar(&cfg.FlagLogLevel, "log", "debug", "Log level")
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
Root.AddCommand(gen.CMD())
Root.AddCommand(lsp.CMD())
err := Root.ExecuteContext(ctx)
assert.NoError(err)
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package apidocs
import (
"strconv"
"strings"
)
type ApiDocumentationGenerator struct{}
func (input *ApiDocumentationGenerator) String() string {
return "You are APIDocBot, an intelligent documentation generator that creates comprehensive, up-to-date API documentation with interactive examples, versioning support, and developer-friendly formatting."
}
type ApiOverview struct {
Api struct {
Name string
Version string
BaseUrl string
Protocol string
AuthType string
Deprecated bool
ReplacementVersion string
SunsetDate string
Beta bool
StabilityLevel string
}
Changelog struct {
HasBreakingChanges bool
BreakingChanges string
NewFeatures string
Improvements string
BugFixes string
}
Docs struct {
MigrationUrl string
}
Auth struct {
Type string
Required bool
DashboardUrl string
OauthFlow string
Scopes string
AuthUrl string
TokenUrl string
ScopeCount int
ScopeList string
HeaderName string
QueryParam string
RateLimits bool
}
RateLimits struct {
StandardRequests int
StandardWindow string
PremiumRequests int
PremiumWindow string
HasBurst bool
BurstRequests int
BurstWindow string
}
}
func (input *ApiOverview) isApiZero() bool {
return input.Api.Name == "" &&
input.Api.Version == "" &&
input.Api.BaseUrl == "" &&
input.Api.Protocol == "" &&
input.Api.AuthType == "" &&
!input.Api.Deprecated &&
input.Api.ReplacementVersion == "" &&
input.Api.SunsetDate == "" &&
!input.Api.Beta &&
input.Api.StabilityLevel == ""
}
func (input *ApiOverview) isChangelogZero() bool {
return !input.Changelog.HasBreakingChanges &&
input.Changelog.BreakingChanges == "" &&
input.Changelog.NewFeatures == "" &&
input.Changelog.Improvements == "" &&
input.Changelog.BugFixes == ""
}
func (input *ApiOverview) isDocsZero() bool {
return input.Docs.MigrationUrl == ""
}
func (input *ApiOverview) isAuthZero() bool {
return input.Auth.Type == "" &&
!input.Auth.Required &&
input.Auth.DashboardUrl == "" &&
input.Auth.OauthFlow == "" &&
input.Auth.Scopes == "" &&
input.Auth.AuthUrl == "" &&
input.Auth.TokenUrl == "" &&
input.Auth.ScopeCount == 0 &&
input.Auth.ScopeList == "" &&
input.Auth.HeaderName == "" &&
input.Auth.QueryParam == "" &&
!input.Auth.RateLimits
}
func (input *ApiOverview) isRateLimitsZero() bool {
return input.RateLimits.StandardRequests == 0 &&
input.RateLimits.StandardWindow == "" &&
input.RateLimits.PremiumRequests == 0 &&
input.RateLimits.PremiumWindow == "" &&
!input.RateLimits.HasBurst &&
input.RateLimits.BurstRequests == 0 &&
input.RateLimits.BurstWindow == ""
}
func (input *ApiOverview) String() string {
var b strings.Builder
var0 := strconv.FormatBool(input.Auth.Required)
var1 := strconv.Itoa(input.RateLimits.StandardRequests)
var2 := strconv.Itoa(input.RateLimits.PremiumRequests)
var3 := strconv.Itoa(input.RateLimits.BurstRequests)
length := 0
length += 7
length += len(input.Api.Name)
length += 33
length += len(input.Api.Version)
length += 16
length += len(input.Api.BaseUrl)
length += 16
length += len(input.Api.Protocol)
length += 21
length += len(input.Api.AuthType)
length += 2
if input.Api.Deprecated {
length += 86
length += len(input.Api.ReplacementVersion)
length += 19
length += len(input.Api.SunsetDate)
length += 1
}
length += 2
if input.Api.Beta {
length += 92
length += len(input.Api.StabilityLevel)
length += 3
}
length += 25
length += len(input.Api.Version)
length += 2
if input.Changelog.HasBreakingChanges {
length += 27
length += len(input.Changelog.BreakingChanges)
length += 59
length += len(input.Docs.MigrationUrl)
length += 28
}
length += 2
if input.Changelog.NewFeatures != "" {
length += 22
length += len(input.Changelog.NewFeatures)
length += 1
}
length += 2
if input.Changelog.Improvements != "" {
length += 23
length += len(input.Changelog.Improvements)
length += 1
}
length += 2
if input.Changelog.BugFixes != "" {
length += 20
length += len(input.Changelog.BugFixes)
length += 1
}
length += 36
length += len(input.Auth.Type)
length += 15
length += len(var0)
length += 2
if input.Auth.Type == "bearer" {
length += 176
length += len(input.Auth.DashboardUrl)
length += 2
}
length += 2
if input.Auth.Type == "oauth2" {
length += 40
length += len(input.Auth.OauthFlow)
length += 13
length += len(input.Auth.Scopes)
length += 26
length += len(input.Auth.AuthUrl)
length += 18
length += len(input.Auth.TokenUrl)
length += 25
if input.Auth.ScopeCount != 0 {
length += 1
length += len(input.Auth.ScopeList)
length += 1
}
length += 1
}
length += 2
if input.Auth.Type == "apikey" {
length += 41
length += len(input.Auth.HeaderName)
length += 24
length += len(input.Auth.QueryParam)
length += 25
length += len(input.Auth.HeaderName)
length += 19
}
length += 2
if input.Auth.RateLimits {
length += 41
length += len(var1)
length += 10
length += len(input.RateLimits.StandardWindow)
length += 19
length += len(var2)
length += 10
length += len(input.RateLimits.PremiumWindow)
length += 165
if input.RateLimits.HasBurst {
length += 22
length += len(var3)
length += 13
length += len(input.RateLimits.BurstWindow)
length += 9
}
length += 1
}
b.Grow(length)
b.WriteString("# ๐ก ")
b.WriteString(input.Api.Name)
b.WriteString(" API Documentation\n\n**Version**: ")
b.WriteString(input.Api.Version)
b.WriteString("\n**Base URL**: `")
b.WriteString(input.Api.BaseUrl)
b.WriteString("`\n**Protocol**: ")
b.WriteString(input.Api.Protocol)
b.WriteString("\n**Authentication**: ")
b.WriteString(input.Api.AuthType)
b.WriteString("\n\n")
if input.Api.Deprecated {
b.WriteString("\nโ ๏ธ **DEPRECATED API**: This API version is deprecated. Please migrate to version ")
b.WriteString(input.Api.ReplacementVersion)
b.WriteString(".\n**Sunset Date**: ")
b.WriteString(input.Api.SunsetDate)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Api.Beta {
b.WriteString("\n๐งช **BETA API**: This API is in beta. Features may change without notice.\n**Stability**: ")
b.WriteString(input.Api.StabilityLevel)
b.WriteString("/5\n")
}
b.WriteString("\n\n## ๐ What's New in v")
b.WriteString(input.Api.Version)
b.WriteString("\n\n")
if input.Changelog.HasBreakingChanges {
b.WriteString("\n### ๐ฅ Breaking Changes\n")
b.WriteString(input.Changelog.BreakingChanges)
b.WriteString("\n\nโ ๏ธ **Migration Required**: See our [migration guide](")
b.WriteString(input.Docs.MigrationUrl)
b.WriteString(") for upgrade instructions.\n")
}
b.WriteString("\n\n")
if input.Changelog.NewFeatures != "" {
b.WriteString("\n### โจ New Features\n")
b.WriteString(input.Changelog.NewFeatures)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Changelog.Improvements != "" {
b.WriteString("\n### ๐ Improvements\n")
b.WriteString(input.Changelog.Improvements)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Changelog.BugFixes != "" {
b.WriteString("\n### ๐ Bug Fixes\n")
b.WriteString(input.Changelog.BugFixes)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ Authentication\n\n**Type**: ")
b.WriteString(input.Auth.Type)
b.WriteString("\n**Required**: ")
b.WriteString(var0)
b.WriteString("\n\n")
if input.Auth.Type == "bearer" {
b.WriteString("\n### Bearer Token Authentication\nInclude your API key in the Authorization header:\n\n```http\nAuthorization: Bearer YOUR_API_KEY\n```\n\n**Get your API key**: [Developer Dashboard](")
b.WriteString(input.Auth.DashboardUrl)
b.WriteString(")\n")
}
b.WriteString("\n\n")
if input.Auth.Type == "oauth2" {
b.WriteString("\n### OAuth 2.0 Authentication\n**Flow**: ")
b.WriteString(input.Auth.OauthFlow)
b.WriteString("\n**Scopes**: ")
b.WriteString(input.Auth.Scopes)
b.WriteString("\n\n**Authorization URL**: `")
b.WriteString(input.Auth.AuthUrl)
b.WriteString("`\n**Token URL**: `")
b.WriteString(input.Auth.TokenUrl)
b.WriteString("`\n\n#### Required Scopes:\n")
if input.Auth.ScopeCount != 0 {
b.WriteRune('\n')
b.WriteString(input.Auth.ScopeList)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Auth.Type == "apikey" {
b.WriteString("\n### API Key Authentication\n**Header**: `")
b.WriteString(input.Auth.HeaderName)
b.WriteString("`\n**Query Parameter**: `")
b.WriteString(input.Auth.QueryParam)
b.WriteString("` (alternative)\n\n```http\n")
b.WriteString(input.Auth.HeaderName)
b.WriteString(": YOUR_API_KEY\n```\n")
}
b.WriteString("\n\n")
if input.Auth.RateLimits {
b.WriteString("\n## ๐ Rate Limits\n\n**Standard Tier**: ")
b.WriteString(var1)
b.WriteString(" requests/")
b.WriteString(input.RateLimits.StandardWindow)
b.WriteString("\n**Premium Tier**: ")
b.WriteString(var2)
b.WriteString(" requests/")
b.WriteString(input.RateLimits.PremiumWindow)
b.WriteString("\n\n**Rate Limit Headers**:\n- `X-RateLimit-Limit`: Total requests allowed\n- `X-RateLimit-Remaining`: Requests remaining\n- `X-RateLimit-Reset`: Time when limit resets\n\n")
if input.RateLimits.HasBurst {
b.WriteString("\n**Burst Allowance**: ")
b.WriteString(var3)
b.WriteString(" requests in ")
b.WriteString(input.RateLimits.BurstWindow)
b.WriteString(" seconds\n")
}
b.WriteRune('\n')
}
return b.String()
}
type EndpointDocumentation struct {
Endpoints struct {
Count int
HasCategories bool
UserManagement bool
UserCount int
DataOperations bool
DataCount int
Analytics bool
AnalyticsCount int
Webhooks bool
WebhookCount int
}
Endpoint struct {
Featured bool
Name string
Method string
Path string
Description string
RequiresAuth bool
HasPathParams bool
PathParams string
ParamName string
ParamType string
ParamRequired bool
ParamDescription string
HasQueryParams bool
QueryParams string
QueryName string
QueryType string
QueryRequired bool
QueryDefault string
QueryDescription string
HasBody bool
RequestBody string
RequestSchema string
SuccessCodes string
ResponseExample string
HasErrorCodes bool
ErrorCodes string
ErrorCode int
ErrorDescription string
ErrorSolution string
CurlBody string
JsBody string
PythonBody string
MethodLower string
NoBody bool
GoBody string
}
Api struct {
BaseUrl string
}
Examples struct {
Curl bool
Javascript bool
Python bool
Go bool
}
}
func (input *EndpointDocumentation) isEndpointsZero() bool {
return input.Endpoints.Count == 0 &&
!input.Endpoints.HasCategories &&
!input.Endpoints.UserManagement &&
input.Endpoints.UserCount == 0 &&
!input.Endpoints.DataOperations &&
input.Endpoints.DataCount == 0 &&
!input.Endpoints.Analytics &&
input.Endpoints.AnalyticsCount == 0 &&
!input.Endpoints.Webhooks &&
input.Endpoints.WebhookCount == 0
}
func (input *EndpointDocumentation) isEndpointZero() bool {
return !input.Endpoint.Featured &&
input.Endpoint.Name == "" &&
input.Endpoint.Method == "" &&
input.Endpoint.Path == "" &&
input.Endpoint.Description == "" &&
!input.Endpoint.RequiresAuth &&
!input.Endpoint.HasPathParams &&
input.Endpoint.PathParams == "" &&
input.Endpoint.ParamName == "" &&
input.Endpoint.ParamType == "" &&
!input.Endpoint.ParamRequired &&
input.Endpoint.ParamDescription == "" &&
!input.Endpoint.HasQueryParams &&
input.Endpoint.QueryParams == "" &&
input.Endpoint.QueryName == "" &&
input.Endpoint.QueryType == "" &&
!input.Endpoint.QueryRequired &&
input.Endpoint.QueryDefault == "" &&
input.Endpoint.QueryDescription == "" &&
!input.Endpoint.HasBody &&
input.Endpoint.RequestBody == "" &&
input.Endpoint.RequestSchema == "" &&
input.Endpoint.SuccessCodes == "" &&
input.Endpoint.ResponseExample == "" &&
!input.Endpoint.HasErrorCodes &&
input.Endpoint.ErrorCodes == "" &&
input.Endpoint.ErrorCode == 0 &&
input.Endpoint.ErrorDescription == "" &&
input.Endpoint.ErrorSolution == "" &&
input.Endpoint.CurlBody == "" &&
input.Endpoint.JsBody == "" &&
input.Endpoint.PythonBody == "" &&
input.Endpoint.MethodLower == "" &&
!input.Endpoint.NoBody &&
input.Endpoint.GoBody == ""
}
func (input *EndpointDocumentation) isApiZero() bool {
return input.Api.BaseUrl == ""
}
func (input *EndpointDocumentation) isExamplesZero() bool {
return !input.Examples.Curl &&
!input.Examples.Javascript &&
!input.Examples.Python &&
!input.Examples.Go
}
func (input *EndpointDocumentation) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Endpoints.Count)
var1 := strconv.Itoa(input.Endpoints.UserCount)
var2 := strconv.Itoa(input.Endpoints.DataCount)
var3 := strconv.Itoa(input.Endpoints.AnalyticsCount)
var4 := strconv.Itoa(input.Endpoints.WebhookCount)
var5 := strconv.FormatBool(input.Endpoint.ParamRequired)
var6 := strconv.FormatBool(input.Endpoint.QueryRequired)
var7 := strconv.Itoa(input.Endpoint.ErrorCode)
length := 0
if input.Endpoints.Count != 0 {
length += 30
length += len(var0)
length += 9
if input.Endpoints.HasCategories {
length += 26
if input.Endpoints.UserManagement {
length += 29
length += len(var1)
length += 11
}
length += 1
if input.Endpoints.DataOperations {
length += 29
length += len(var2)
length += 13
}
length += 1
if input.Endpoints.Analytics {
length += 23
length += len(var3)
length += 11
}
length += 1
if input.Endpoints.Webhooks {
length += 22
length += len(var4)
length += 11
}
length += 1
}
length += 1
}
length += 2
if input.Endpoint.Featured {
length += 28
length += len(input.Endpoint.Name)
length += 15
length += len(input.Endpoint.Method)
length += 13
length += len(input.Endpoint.Path)
length += 15
length += len(input.Endpoint.Description)
length += 23
length += len(input.Endpoint.Method)
length += 1
length += len(input.Api.BaseUrl)
length += len(input.Endpoint.Path)
length += 32
if input.Endpoint.RequiresAuth {
length += 36
}
length += 6
if input.Endpoint.HasPathParams {
length += 114
if input.Endpoint.PathParams != "" {
length += 3
length += len(input.Endpoint.ParamName)
length += 3
length += len(input.Endpoint.ParamType)
length += 3
length += len(var5)
length += 3
length += len(input.Endpoint.ParamDescription)
length += 3
}
length += 1
}
length += 2
if input.Endpoint.HasQueryParams {
length += 135
if input.Endpoint.QueryParams != "" {
length += 3
length += len(input.Endpoint.QueryName)
length += 3
length += len(input.Endpoint.QueryType)
length += 3
length += len(var6)
length += 3
length += len(input.Endpoint.QueryDefault)
length += 3
length += len(input.Endpoint.QueryDescription)
length += 3
}
length += 1
}
length += 2
if input.Endpoint.HasBody {
length += 28
length += len(input.Endpoint.RequestBody)
length += 18
length += len(input.Endpoint.RequestSchema)
length += 1
}
length += 16
if input.Endpoint.SuccessCodes != "" {
length += 20
length += len(input.Endpoint.SuccessCodes)
length += 1
}
length += 10
length += len(input.Endpoint.ResponseExample)
length += 6
if input.Endpoint.HasErrorCodes {
length += 91
if input.Endpoint.ErrorCodes != "" {
length += 3
length += len(var7)
length += 3
length += len(input.Endpoint.ErrorDescription)
length += 3
length += len(input.Endpoint.ErrorSolution)
length += 3
}
length += 1
}
length += 21
if input.Examples.Curl {
length += 27
length += len(input.Endpoint.Method)
length += 6
length += len(input.Api.BaseUrl)
length += len(input.Endpoint.Path)
length += 44
if input.Endpoint.RequiresAuth {
length += 45
}
length += 1
if input.Endpoint.HasBody {
length += 7
length += len(input.Endpoint.CurlBody)
length += 2
}
length += 5
}
length += 2
if input.Examples.Javascript {
length += 69
length += len(input.Api.BaseUrl)
length += len(input.Endpoint.Path)
length += 16
length += len(input.Endpoint.Method)
length += 56
if input.Endpoint.RequiresAuth {
length += 45
}
length += 6
if input.Endpoint.HasBody {
length += 24
length += len(input.Endpoint.JsBody)
length += 2
}
length += 65
}
length += 2
if input.Examples.Python {
length += 58
length += len(input.Api.BaseUrl)
length += len(input.Endpoint.Path)
length += 2
if input.Endpoint.RequiresAuth {
length += 98
}
length += 1
if input.Endpoint.HasBody {
length += 8
length += len(input.Endpoint.PythonBody)
length += 22
length += len(input.Endpoint.MethodLower)
length += 34
}
length += 1
if input.Endpoint.NoBody {
length += 21
length += len(input.Endpoint.MethodLower)
length += 23
}
length += 28
}
length += 2
if input.Examples.Go {
length += 124
length += len(input.Api.BaseUrl)
length += len(input.Endpoint.Path)
length += 7
if input.Endpoint.HasBody {
length += 13
length += len(input.Endpoint.GoBody)
length += 75
length += len(input.Endpoint.Method)
length += 35
}
length += 1
if input.Endpoint.NoBody {
length += 32
length += len(input.Endpoint.Method)
length += 13
}
length += 61
if input.Endpoint.RequiresAuth {
length += 60
}
length += 130
}
length += 1
}
b.Grow(length)
if input.Endpoints.Count != 0 {
b.WriteString("\n## ๐ Available Endpoints (")
b.WriteString(var0)
b.WriteString(" total)\n\n")
if input.Endpoints.HasCategories {
b.WriteString("\n### Endpoint Categories:\n")
if input.Endpoints.UserManagement {
b.WriteString("\n- **๐ค User Management**: ")
b.WriteString(var1)
b.WriteString(" endpoints\n")
}
b.WriteRune('\n')
if input.Endpoints.DataOperations {
b.WriteString("\n- **๐พ Data Operations**: ")
b.WriteString(var2)
b.WriteString(" endpoints \n")
}
b.WriteRune('\n')
if input.Endpoints.Analytics {
b.WriteString("\n- **๐ Analytics**: ")
b.WriteString(var3)
b.WriteString(" endpoints\n")
}
b.WriteRune('\n')
if input.Endpoints.Webhooks {
b.WriteString("\n- **๐ Webhooks**: ")
b.WriteString(var4)
b.WriteString(" endpoints\n")
}
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Endpoint.Featured {
b.WriteString("\n## ๐ Featured Endpoint: ")
b.WriteString(input.Endpoint.Name)
b.WriteString("\n\n**Method**: `")
b.WriteString(input.Endpoint.Method)
b.WriteString("`\n**Path**: `")
b.WriteString(input.Endpoint.Path)
b.WriteString("`\n**Purpose**: ")
b.WriteString(input.Endpoint.Description)
b.WriteString("\n\n### Request\n\n```http\n")
b.WriteString(input.Endpoint.Method)
b.WriteString(" ")
b.WriteString(input.Api.BaseUrl)
b.WriteString(input.Endpoint.Path)
b.WriteString("\nContent-Type: application/json\n")
if input.Endpoint.RequiresAuth {
b.WriteString("\nAuthorization: Bearer YOUR_API_KEY\n")
}
b.WriteString("\n```\n\n")
if input.Endpoint.HasPathParams {
b.WriteString("\n#### Path Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n")
if input.Endpoint.PathParams != "" {
b.WriteString("\n| ")
b.WriteString(input.Endpoint.ParamName)
b.WriteString(" | ")
b.WriteString(input.Endpoint.ParamType)
b.WriteString(" | ")
b.WriteString(var5)
b.WriteString(" | ")
b.WriteString(input.Endpoint.ParamDescription)
b.WriteString(" |\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Endpoint.HasQueryParams {
b.WriteString("\n#### Query Parameters\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n")
if input.Endpoint.QueryParams != "" {
b.WriteString("\n| ")
b.WriteString(input.Endpoint.QueryName)
b.WriteString(" | ")
b.WriteString(input.Endpoint.QueryType)
b.WriteString(" | ")
b.WriteString(var6)
b.WriteString(" | ")
b.WriteString(input.Endpoint.QueryDefault)
b.WriteString(" | ")
b.WriteString(input.Endpoint.QueryDescription)
b.WriteString(" |\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Endpoint.HasBody {
b.WriteString("\n#### Request Body\n\n```json\n")
b.WriteString(input.Endpoint.RequestBody)
b.WriteString("\n```\n\n**Schema**: ")
b.WriteString(input.Endpoint.RequestSchema)
b.WriteRune('\n')
}
b.WriteString("\n\n### Response\n\n")
if input.Endpoint.SuccessCodes != "" {
b.WriteString("\n**Success Codes**: ")
b.WriteString(input.Endpoint.SuccessCodes)
b.WriteRune('\n')
}
b.WriteString("\n\n```json\n")
b.WriteString(input.Endpoint.ResponseExample)
b.WriteString("\n```\n\n")
if input.Endpoint.HasErrorCodes {
b.WriteString("\n#### Error Responses\n\n| Code | Description | Solution |\n|------|-------------|----------|\n")
if input.Endpoint.ErrorCodes != "" {
b.WriteString("\n| ")
b.WriteString(var7)
b.WriteString(" | ")
b.WriteString(input.Endpoint.ErrorDescription)
b.WriteString(" | ")
b.WriteString(input.Endpoint.ErrorSolution)
b.WriteString(" |\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n### Code Examples\n\n")
if input.Examples.Curl {
b.WriteString("\n#### cURL\n```bash\ncurl -X ")
b.WriteString(input.Endpoint.Method)
b.WriteString(" \\\n '")
b.WriteString(input.Api.BaseUrl)
b.WriteString(input.Endpoint.Path)
b.WriteString("' \\\n -H 'Content-Type: application/json' \\\n")
if input.Endpoint.RequiresAuth {
b.WriteString("\n -H 'Authorization: Bearer YOUR_API_KEY' \\\n")
}
b.WriteRune('\n')
if input.Endpoint.HasBody {
b.WriteString("\n -d '")
b.WriteString(input.Endpoint.CurlBody)
b.WriteString("'\n")
}
b.WriteString("\n```\n")
}
b.WriteString("\n\n")
if input.Examples.Javascript {
b.WriteString("\n#### JavaScript (fetch)\n```javascript\nconst response = await fetch('")
b.WriteString(input.Api.BaseUrl)
b.WriteString(input.Endpoint.Path)
b.WriteString("', {\n method: '")
b.WriteString(input.Endpoint.Method)
b.WriteString("',\n headers: {\n 'Content-Type': 'application/json',\n")
if input.Endpoint.RequiresAuth {
b.WriteString("\n 'Authorization': 'Bearer YOUR_API_KEY',\n")
}
b.WriteString("\n },\n")
if input.Endpoint.HasBody {
b.WriteString("\n body: JSON.stringify(")
b.WriteString(input.Endpoint.JsBody)
b.WriteString(")\n")
}
b.WriteString("\n});\n\nconst data = await response.json();\nconsole.log(data);\n```\n")
}
b.WriteString("\n\n")
if input.Examples.Python {
b.WriteString("\n#### Python (requests)\n```python\nimport requests\n\nurl = \"")
b.WriteString(input.Api.BaseUrl)
b.WriteString(input.Endpoint.Path)
b.WriteString("\"\n")
if input.Endpoint.RequiresAuth {
b.WriteString("\nheaders = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer YOUR_API_KEY\"\n}\n")
}
b.WriteRune('\n')
if input.Endpoint.HasBody {
b.WriteString("\ndata = ")
b.WriteString(input.Endpoint.PythonBody)
b.WriteString("\n\nresponse = requests.")
b.WriteString(input.Endpoint.MethodLower)
b.WriteString("(url, headers=headers, json=data)\n")
}
b.WriteRune('\n')
if input.Endpoint.NoBody {
b.WriteString("\nresponse = requests.")
b.WriteString(input.Endpoint.MethodLower)
b.WriteString("(url, headers=headers)\n")
}
b.WriteString("\nprint(response.json())\n```\n")
}
b.WriteString("\n\n")
if input.Examples.Go {
b.WriteString("\n#### Go\n```go\npackage main\n\nimport (\n \"bytes\"\n \"encoding/json\"\n \"fmt\"\n \"net/http\"\n)\n\nfunc main() {\n url := \"")
b.WriteString(input.Api.BaseUrl)
b.WriteString(input.Endpoint.Path)
b.WriteString("\"\n \n")
if input.Endpoint.HasBody {
b.WriteString("\n data := ")
b.WriteString(input.Endpoint.GoBody)
b.WriteString("\n jsonData, _ := json.Marshal(data)\n \n req, _ := http.NewRequest(\"")
b.WriteString(input.Endpoint.Method)
b.WriteString("\", url, bytes.NewBuffer(jsonData))\n")
}
b.WriteRune('\n')
if input.Endpoint.NoBody {
b.WriteString("\n req, _ := http.NewRequest(\"")
b.WriteString(input.Endpoint.Method)
b.WriteString("\", url, nil)\n")
}
b.WriteString("\n \n req.Header.Set(\"Content-Type\", \"application/json\")\n")
if input.Endpoint.RequiresAuth {
b.WriteString("\n req.Header.Set(\"Authorization\", \"Bearer YOUR_API_KEY\")\n")
}
b.WriteString("\n \n client := &http.Client{}\n resp, _ := client.Do(req)\n defer resp.Body.Close()\n \n // Handle response...\n}\n```\n")
}
b.WriteRune('\n')
}
return b.String()
}
type SchemaReference struct {
Schemas struct {
Available bool
Count int
Schema1 struct {
Name string
Description string
Example string
Properties string
HasValidation bool
ValidationRules string
}
FieldName string
FieldType string
FieldRequired bool
FieldDescription string
}
}
func (input *SchemaReference) isSchemasZero() bool {
return !input.Schemas.Available &&
input.Schemas.Count == 0 &&
input.isSchemasSchema1Zero() &&
input.Schemas.FieldName == "" &&
input.Schemas.FieldType == "" &&
!input.Schemas.FieldRequired &&
input.Schemas.FieldDescription == ""
}
func (input *SchemaReference) isSchemasSchema1Zero() bool {
return input.Schemas.Schema1.Name == "" &&
input.Schemas.Schema1.Description == "" &&
input.Schemas.Schema1.Example == "" &&
input.Schemas.Schema1.Properties == "" &&
!input.Schemas.Schema1.HasValidation &&
input.Schemas.Schema1.ValidationRules == ""
}
func (input *SchemaReference) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Schemas.Count)
var1 := strconv.FormatBool(input.Schemas.FieldRequired)
length := 0
if input.Schemas.Available {
length += 23
if input.Schemas.Count != 0 {
length += 24
length += len(var0)
length += 9
if !input.isSchemasSchema1Zero() {
length += 6
length += len(input.Schemas.Schema1.Name)
length += 19
length += len(input.Schemas.Schema1.Description)
length += 10
length += len(input.Schemas.Schema1.Example)
length += 106
if input.Schemas.Schema1.Properties != "" {
length += 3
length += len(input.Schemas.FieldName)
length += 3
length += len(input.Schemas.FieldType)
length += 3
length += len(var1)
length += 3
length += len(input.Schemas.FieldDescription)
length += 3
}
length += 2
if input.Schemas.Schema1.HasValidation {
length += 23
length += len(input.Schemas.Schema1.ValidationRules)
length += 1
}
length += 1
}
length += 1
}
length += 1
}
b.Grow(length)
if input.Schemas.Available {
b.WriteString("\n## ๐ Data Schemas\n\n")
if input.Schemas.Count != 0 {
b.WriteString("\n### Available Schemas (")
b.WriteString(var0)
b.WriteString(" total)\n\n")
if !input.isSchemasSchema1Zero() {
b.WriteString("\n#### ")
b.WriteString(input.Schemas.Schema1.Name)
b.WriteString("\n\n**Description**: ")
b.WriteString(input.Schemas.Schema1.Description)
b.WriteString("\n\n```json\n")
b.WriteString(input.Schemas.Schema1.Example)
b.WriteString("\n```\n\n**Properties**:\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n")
if input.Schemas.Schema1.Properties != "" {
b.WriteString("\n| ")
b.WriteString(input.Schemas.FieldName)
b.WriteString(" | ")
b.WriteString(input.Schemas.FieldType)
b.WriteString(" | ")
b.WriteString(var1)
b.WriteString(" | ")
b.WriteString(input.Schemas.FieldDescription)
b.WriteString(" |\n")
}
b.WriteString("\n\n")
if input.Schemas.Schema1.HasValidation {
b.WriteString("\n**Validation Rules**:\n")
b.WriteString(input.Schemas.Schema1.ValidationRules)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteRune('\n')
}
return b.String()
}
type SdksAndTools struct {
Sdks struct {
Available bool
OfficialCount int
Javascript bool
JsPackage string
JsDocs string
JsVersion string
JsSize string
Python bool
PythonPackage string
PythonDocs string
PythonVersion string
PythonCompat string
Go bool
GoPackage string
GoDocs string
GoVersion string
GoCompat string
Ruby bool
RubyPackage string
RubyDocs string
RubyVersion string
CommunityCount int
CommunityList string
}
Tools struct {
Postman bool
PostmanUrl string
Openapi bool
OpenapiUrl string
SwaggerUrl string
Insomnia bool
InsomniaUrl string
}
}
func (input *SdksAndTools) isSdksZero() bool {
return !input.Sdks.Available &&
input.Sdks.OfficialCount == 0 &&
!input.Sdks.Javascript &&
input.Sdks.JsPackage == "" &&
input.Sdks.JsDocs == "" &&
input.Sdks.JsVersion == "" &&
input.Sdks.JsSize == "" &&
!input.Sdks.Python &&
input.Sdks.PythonPackage == "" &&
input.Sdks.PythonDocs == "" &&
input.Sdks.PythonVersion == "" &&
input.Sdks.PythonCompat == "" &&
!input.Sdks.Go &&
input.Sdks.GoPackage == "" &&
input.Sdks.GoDocs == "" &&
input.Sdks.GoVersion == "" &&
input.Sdks.GoCompat == "" &&
!input.Sdks.Ruby &&
input.Sdks.RubyPackage == "" &&
input.Sdks.RubyDocs == "" &&
input.Sdks.RubyVersion == "" &&
input.Sdks.CommunityCount == 0 &&
input.Sdks.CommunityList == ""
}
func (input *SdksAndTools) isToolsZero() bool {
return !input.Tools.Postman &&
input.Tools.PostmanUrl == "" &&
!input.Tools.Openapi &&
input.Tools.OpenapiUrl == "" &&
input.Tools.SwaggerUrl == "" &&
!input.Tools.Insomnia &&
input.Tools.InsomniaUrl == ""
}
func (input *SdksAndTools) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Sdks.OfficialCount)
var1 := strconv.Itoa(input.Sdks.CommunityCount)
length := 0
if input.Sdks.Available {
length += 30
if input.Sdks.OfficialCount != 0 {
length += 20
length += len(var0)
length += 13
if input.Sdks.Javascript {
length += 48
length += len(input.Sdks.JsPackage)
length += 48
length += len(input.Sdks.JsDocs)
length += 15
length += len(input.Sdks.JsVersion)
length += 18
length += len(input.Sdks.JsSize)
length += 1
}
length += 2
if input.Sdks.Python {
length += 33
length += len(input.Sdks.PythonPackage)
length += 44
length += len(input.Sdks.PythonDocs)
length += 15
length += len(input.Sdks.PythonVersion)
length += 27
length += len(input.Sdks.PythonCompat)
length += 1
}
length += 2
if input.Sdks.Go {
length += 24
length += len(input.Sdks.GoPackage)
length += 40
length += len(input.Sdks.GoDocs)
length += 15
length += len(input.Sdks.GoVersion)
length += 17
length += len(input.Sdks.GoCompat)
length += 1
}
length += 2
if input.Sdks.Ruby {
length += 31
length += len(input.Sdks.RubyPackage)
length += 42
length += len(input.Sdks.RubyDocs)
length += 15
length += len(input.Sdks.RubyVersion)
length += 1
}
length += 1
}
length += 2
if input.Sdks.CommunityCount != 0 {
length += 21
length += len(var1)
length += 12
length += len(input.Sdks.CommunityList)
length += 84
}
length += 1
}
length += 29
if input.Tools.Postman {
length += 90
length += len(input.Tools.PostmanUrl)
length += 107
}
length += 2
if input.Tools.Openapi {
length += 60
length += len(input.Tools.OpenapiUrl)
length += 46
length += len(input.Tools.SwaggerUrl)
length += 2
}
length += 2
if input.Tools.Insomnia {
length += 66
length += len(input.Tools.InsomniaUrl)
length += 2
}
b.Grow(length)
if input.Sdks.Available {
b.WriteString("\n## ๐ ๏ธ SDKs & Libraries\n\n")
if input.Sdks.OfficialCount != 0 {
b.WriteString("\n### Official SDKs (")
b.WriteString(var0)
b.WriteString(" languages)\n\n")
if input.Sdks.Javascript {
b.WriteString("\n#### JavaScript/TypeScript\n```bash\nnpm install ")
b.WriteString(input.Sdks.JsPackage)
b.WriteString("\n```\n\n**Documentation**: [JavaScript SDK Guide](")
b.WriteString(input.Sdks.JsDocs)
b.WriteString(")\n**Version**: ")
b.WriteString(input.Sdks.JsVersion)
b.WriteString("\n**Bundle Size**: ")
b.WriteString(input.Sdks.JsSize)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Sdks.Python {
b.WriteString("\n#### Python\n```bash\npip install ")
b.WriteString(input.Sdks.PythonPackage)
b.WriteString("\n```\n\n**Documentation**: [Python SDK Guide](")
b.WriteString(input.Sdks.PythonDocs)
b.WriteString(")\n**Version**: ")
b.WriteString(input.Sdks.PythonVersion)
b.WriteString("\n**Python Compatibility**: ")
b.WriteString(input.Sdks.PythonCompat)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Sdks.Go {
b.WriteString("\n#### Go\n```bash\ngo get ")
b.WriteString(input.Sdks.GoPackage)
b.WriteString("\n```\n\n**Documentation**: [Go SDK Guide](")
b.WriteString(input.Sdks.GoDocs)
b.WriteString(")\n**Version**: ")
b.WriteString(input.Sdks.GoVersion)
b.WriteString("\n**Go Version**: ")
b.WriteString(input.Sdks.GoCompat)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Sdks.Ruby {
b.WriteString("\n#### Ruby\n```bash\ngem install ")
b.WriteString(input.Sdks.RubyPackage)
b.WriteString("\n```\n\n**Documentation**: [Ruby SDK Guide](")
b.WriteString(input.Sdks.RubyDocs)
b.WriteString(")\n**Version**: ")
b.WriteString(input.Sdks.RubyVersion)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Sdks.CommunityCount != 0 {
b.WriteString("\n### Community SDKs (")
b.WriteString(var1)
b.WriteString(" languages)\n")
b.WriteString(input.Sdks.CommunityList)
b.WriteString("\n\n*Note: Community SDKs are maintained by third parties and may not be up-to-date.*\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ง Development Tools\n\n")
if input.Tools.Postman {
b.WriteString("\n### Postman Collection\nImport our complete API collection: [Download Postman Collection](")
b.WriteString(input.Tools.PostmanUrl)
b.WriteString(")\n\n**Features**:\n- Pre-configured environments\n- Authentication setup\n- Example requests for all endpoints\n")
}
b.WriteString("\n\n")
if input.Tools.Openapi {
b.WriteString("\n### OpenAPI Specification\n**OpenAPI 3.0**: [Download Spec](")
b.WriteString(input.Tools.OpenapiUrl)
b.WriteString(")\n**Swagger UI**: [Interactive Documentation](")
b.WriteString(input.Tools.SwaggerUrl)
b.WriteString(")\n")
}
b.WriteString("\n\n")
if input.Tools.Insomnia {
b.WriteString("\n### Insomnia Workspace\nImport for Insomnia: [Download Workspace](")
b.WriteString(input.Tools.InsomniaUrl)
b.WriteString(")\n")
}
return b.String()
}
type SupportAndResources struct {
Support struct {
HasPremium bool
PremiumResponseTime string
PremiumEmail string
PremiumPhone string
PremiumSlack string
Discord string
StackoverflowTag string
GithubIssues string
StatusPage bool
StatusUrl string
SlaUptime string
CurrentStatus string
}
Docs struct {
TutorialsUrl string
BestPracticesUrl string
ChangelogUrl string
Version string
LastUpdated string
}
Api struct {
Version string
}
Company struct {
Name string
}
}
func (input *SupportAndResources) isSupportZero() bool {
return !input.Support.HasPremium &&
input.Support.PremiumResponseTime == "" &&
input.Support.PremiumEmail == "" &&
input.Support.PremiumPhone == "" &&
input.Support.PremiumSlack == "" &&
input.Support.Discord == "" &&
input.Support.StackoverflowTag == "" &&
input.Support.GithubIssues == "" &&
!input.Support.StatusPage &&
input.Support.StatusUrl == "" &&
input.Support.SlaUptime == "" &&
input.Support.CurrentStatus == ""
}
func (input *SupportAndResources) isDocsZero() bool {
return input.Docs.TutorialsUrl == "" &&
input.Docs.BestPracticesUrl == "" &&
input.Docs.ChangelogUrl == "" &&
input.Docs.Version == "" &&
input.Docs.LastUpdated == ""
}
func (input *SupportAndResources) isApiZero() bool {
return input.Api.Version == ""
}
func (input *SupportAndResources) isCompanyZero() bool {
return input.Company.Name == ""
}
func (input *SupportAndResources) String() string {
var b strings.Builder
length := 0
length += 43
if input.Support.HasPremium {
length += 22
length += len(input.Support.PremiumResponseTime)
length += 24
length += len(input.Support.PremiumEmail)
length += 10
length += len(input.Support.PremiumPhone)
length += 10
length += len(input.Support.PremiumSlack)
length += 1
}
length += 36
length += len(input.Support.Discord)
length += 29
length += len(input.Support.StackoverflowTag)
length += 19
length += len(input.Support.GithubIssues)
length += 92
length += len(input.Docs.TutorialsUrl)
length += 23
length += len(input.Docs.BestPracticesUrl)
length += 18
length += len(input.Docs.ChangelogUrl)
length += 2
if input.Support.StatusPage {
length += 46
length += len(input.Support.StatusUrl)
length += 11
length += len(input.Support.SlaUptime)
length += 21
length += len(input.Support.CurrentStatus)
length += 1
}
length += 33
length += len(input.Docs.Version)
length += 19
length += len(input.Docs.LastUpdated)
length += 18
length += len(input.Api.Version)
length += 28
length += len(input.Company.Name)
length += 11
b.Grow(length)
b.WriteString("## ๐ Getting Help\n\n### Support Channels\n")
if input.Support.HasPremium {
b.WriteString("\n**Premium Support**: ")
b.WriteString(input.Support.PremiumResponseTime)
b.WriteString(" response time\n- Email: ")
b.WriteString(input.Support.PremiumEmail)
b.WriteString("\n- Phone: ")
b.WriteString(input.Support.PremiumPhone)
b.WriteString("\n- Slack: ")
b.WriteString(input.Support.PremiumSlack)
b.WriteRune('\n')
}
b.WriteString("\n\n**Community Support**:\n- Discord: ")
b.WriteString(input.Support.Discord)
b.WriteString("\n- Stack Overflow: Tag with `")
b.WriteString(input.Support.StackoverflowTag)
b.WriteString("`\n- GitHub Issues: ")
b.WriteString(input.Support.GithubIssues)
b.WriteString("\n\n### Documentation Resources\n- **API Reference**: You're reading it! ๐\n- **Tutorials**: ")
b.WriteString(input.Docs.TutorialsUrl)
b.WriteString("\n- **Best Practices**: ")
b.WriteString(input.Docs.BestPracticesUrl)
b.WriteString("\n- **Changelog**: ")
b.WriteString(input.Docs.ChangelogUrl)
b.WriteString("\n\n")
if input.Support.StatusPage {
b.WriteString("\n### Service Status\nCheck our service status: ")
b.WriteString(input.Support.StatusUrl)
b.WriteString("\n\n**SLA**: ")
b.WriteString(input.Support.SlaUptime)
b.WriteString("% uptime\n**Status**: ")
b.WriteString(input.Support.CurrentStatus)
b.WriteRune('\n')
}
b.WriteString("\n\n---\n**Documentation Version**: ")
b.WriteString(input.Docs.Version)
b.WriteString("\n**Last Updated**: ")
b.WriteString(input.Docs.LastUpdated)
b.WriteString("\n**API Version**: ")
b.WriteString(input.Api.Version)
b.WriteString("\n\n*Built with โค๏ธ by the ")
b.WriteString(input.Company.Name)
b.WriteString(" API Team* ")
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package bench
import (
"strconv"
"strings"
)
type TestTypeCache struct {
Age int
}
func (input *TestTypeCache) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Age)
length := 0
if input.Age >= 18 {
length += 9
length += len(var0)
length += 11
}
b.Grow(length)
if input.Age >= 18 {
b.WriteString("\nYou are ")
b.WriteString(var0)
b.WriteString(" years old\n")
}
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package chatagent
import (
"strconv"
"strings"
)
type SystemPrompt struct {
AiName string
}
func (input *SystemPrompt) String() string {
var b strings.Builder
length := 0
length += 35
length += len(input.AiName)
length += 280
b.Grow(length)
b.WriteString("You are a friendly assistant named ")
b.WriteString(input.AiName)
b.WriteString(" who can help users with their questions.\nDo not hallucinate. Do not lie. Do not be rude. Do not be inappropriate.\nIf you do not know the answer to a question, please say so.\n2 newlines will produce a newline in the output at the end of the prompt.\nThis prompt uses the technique.")
return b.String()
}
type CreateTitle struct {
UserName string
MessageThread string
}
func (input *CreateTitle) String() string {
var b strings.Builder
length := 0
length += 62
length += len(input.UserName)
length += 2
length += len(input.MessageThread)
length += 7
b.Grow(length)
b.WriteString("Create a title summarizing the contents of this exchange with ")
b.WriteString(input.UserName)
b.WriteString(":\n")
b.WriteString(input.MessageThread)
b.WriteString("\ntitle:")
return b.String()
}
type ChatWithUser struct {
AiName string
MessageThread string
}
func (input *ChatWithUser) String() string {
var b strings.Builder
length := 0
length += 20
length += len(input.AiName)
length += 43
length += len(input.MessageThread)
length += 1
length += len(input.AiName)
length += 1
b.Grow(length)
b.WriteString("You are an AI named ")
b.WriteString(input.AiName)
b.WriteString(". Please respond to the chat thread below:\n")
b.WriteString(input.MessageThread)
b.WriteRune('\n')
b.WriteString(input.AiName)
b.WriteString(":")
return b.String()
}
type ExampleWithManyVariables struct {
UserName string
MessageThread string
AiName string
Title string
MaxLineLen string
MoreVariables string
}
func (input *ExampleWithManyVariables) String() string {
var b strings.Builder
length := 0
length += len(input.UserName)
length += 1
length += len(input.MessageThread)
length += 1
length += len(input.AiName)
length += 1
length += len(input.Title)
length += 32
length += len(input.UserName)
length += 15
length += len(input.AiName)
length += 64
length += len(input.MaxLineLen)
length += 13
length += len(input.MoreVariables)
length += 151
b.Grow(length)
b.WriteString(input.UserName)
b.WriteRune('\n')
b.WriteString(input.MessageThread)
b.WriteRune('\n')
b.WriteString(input.AiName)
b.WriteRune('\n')
b.WriteString(input.Title)
b.WriteString("\nAs the title says, the user is ")
b.WriteString(input.UserName)
b.WriteString(" and the AI is ")
b.WriteString(input.AiName)
b.WriteString(".\nThe default max line length is 80 characters. It is currently ")
b.WriteString(input.MaxLineLen)
b.WriteString(".\nSometimes, ")
b.WriteString(input.MoreVariables)
b.WriteString(" are needed.\nWhen the max line length is surpassed, AgentFlow will wrap the line.\nThis only applies to function headers. Prompt bodies are not wrapped.")
return b.String()
}
type ConditionalPrompting struct {
User struct {
Email string
}
}
func (input *ConditionalPrompting) isUserZero() bool {
return input.User.Email == ""
}
func (input *ConditionalPrompting) String() string {
var b strings.Builder
length := 0
length += 64
if input.User.Email != "" {
length += 21
length += len(input.User.Email)
length += 2
}
b.Grow(length)
b.WriteString("This prompt demonstrates conditional prompting using optionals.\n")
if input.User.Email != "" {
b.WriteString("\nThe user's email is ")
b.WriteString(input.User.Email)
b.WriteString(".\n")
}
return b.String()
}
type ConditionalWithElse struct {
User struct {
Premium bool
Subscription struct {
Tier int
}
}
}
func (input *ConditionalWithElse) isUserZero() bool {
return !input.User.Premium &&
input.isUserSubscriptionZero()
}
func (input *ConditionalWithElse) isUserSubscriptionZero() bool {
return input.User.Subscription.Tier == 0
}
func (input *ConditionalWithElse) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.User.Subscription.Tier)
length := 0
length += 36
if input.User.Premium {
length += 88
length += len(var0)
length += 34
length += len(var0)
length += 2
} else {
length += 80
}
b.Grow(length)
b.WriteString("Here's an example with else syntax:\n")
if input.User.Premium {
b.WriteString("\nWelcome premium user! You have access to all features.\nYour subscription tier is level ")
b.WriteString(var0)
b.WriteString(".\nOnce again, that tier number is ")
b.WriteString(var0)
b.WriteString(".\n")
} else {
b.WriteString("\nWelcome! You're using the basic version. Upgrade to premium for more features.\n")
}
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package creativewriting
import (
"strconv"
"strings"
)
type SystemPrompt struct{}
func (input *SystemPrompt) String() string {
return "You are CreativeWriter Pro, a versatile AI writing assistant specializing in storytelling, creative fiction, and narrative development. You adapt your style and guidance based on genre, target audience, and the writer's experience level."
}
type WritingSessionIntroduction struct {
Writer struct {
Name string
Experience string
FavoriteGenres string
CurrentStreak int
}
Session struct {
Goal string
TargetWords int
}
Project struct {
Title string
Genre string
Audience string
CurrentWords int
CompletionPercentage int
}
}
func (input *WritingSessionIntroduction) isWriterZero() bool {
return input.Writer.Name == "" &&
input.Writer.Experience == "" &&
input.Writer.FavoriteGenres == "" &&
input.Writer.CurrentStreak == 0
}
func (input *WritingSessionIntroduction) isSessionZero() bool {
return input.Session.Goal == "" &&
input.Session.TargetWords == 0
}
func (input *WritingSessionIntroduction) isProjectZero() bool {
return input.Project.Title == "" &&
input.Project.Genre == "" &&
input.Project.Audience == "" &&
input.Project.CurrentWords == 0 &&
input.Project.CompletionPercentage == 0
}
func (input *WritingSessionIntroduction) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Session.TargetWords)
var1 := strconv.Itoa(input.Writer.CurrentStreak)
var2 := strconv.Itoa(input.Project.CurrentWords)
var3 := strconv.Itoa(input.Project.CompletionPercentage)
length := 0
length += 49
length += len(input.Writer.Name)
length += 84
length += len(input.Writer.Experience)
length += 25
length += len(input.Writer.FavoriteGenres)
length += 21
length += len(input.Session.Goal)
length += 26
length += len(var0)
length += 8
if input.Writer.CurrentStreak != 0 {
length += 26
length += len(var1)
length += 20
if input.Writer.CurrentStreak >= 30 {
length += 67
}
length += 37
length += len(input.Project.Title)
length += 12
length += len(input.Project.Genre)
length += 22
length += len(input.Project.Audience)
length += 21
length += len(var2)
length += 8
if input.Project.CompletionPercentage != 0 {
length += 15
length += len(var3)
length += 12
if input.Project.CompletionPercentage >= 75 {
length += 53
}
length += 2
if input.Project.CompletionPercentage < 25 {
length += 65
}
length += 1
}
}
b.Grow(length)
b.WriteString("# โ๏ธ Creative Writing Session\n\nWelcome back, ")
b.WriteString(input.Writer.Name)
b.WriteString("! Let's bring your stories to life.\n\n## ๐ค Writer Profile\n- **Experience Level**: ")
b.WriteString(input.Writer.Experience)
b.WriteString("\n- **Preferred Genres**: ")
b.WriteString(input.Writer.FavoriteGenres)
b.WriteString("\n- **Writing Goal**: ")
b.WriteString(input.Session.Goal)
b.WriteString("\n- **Target Word Count**: ")
b.WriteString(var0)
b.WriteString(" words\n\n")
if input.Writer.CurrentStreak != 0 {
b.WriteString("\n๐ฅ **Writing Streak**: ")
b.WriteString(var1)
b.WriteString(" days! Keep it up!\n\n")
if input.Writer.CurrentStreak >= 30 {
b.WriteString("\n๐ **Master Writer**: 30+ day streak! You're a writing machine!\n")
}
b.WriteString("\n\n## ๐ Current Project\n**Title**: ")
b.WriteString(input.Project.Title)
b.WriteString("\n**Genre**: ")
b.WriteString(input.Project.Genre)
b.WriteString("\n**Target Audience**: ")
b.WriteString(input.Project.Audience)
b.WriteString("\n**Current Length**: ")
b.WriteString(var2)
b.WriteString(" words\n\n")
if input.Project.CompletionPercentage != 0 {
b.WriteString("\n**Progress**: ")
b.WriteString(var3)
b.WriteString("% complete\n\n")
if input.Project.CompletionPercentage >= 75 {
b.WriteString("\n๐ **Almost there!** You're in the final stretch!\n")
}
b.WriteString("\n\n")
if input.Project.CompletionPercentage < 25 {
b.WriteString("\n๐ฑ **Just getting started** - The hardest part is behind you!\n")
}
b.WriteRune('\n')
}
}
return b.String()
}
type GenreSpecificGuidance struct {
Project struct {
Genre string
}
Fantasy struct {
WorldComplexity string
MagicSystem string
TimePeriod string
Creatures string
Locations string
Conflicts string
HasMagicRules bool
MagicRules string
MagicLimitations string
MagicConsequences string
}
Scifi struct {
Subgenre string
TechnologyLevel string
Setting string
Technologies string
ScienceFocus string
Themes string
HasTimeTravel bool
TimeTravelType string
TimeTravelRules string
ParadoxApproach string
}
Mystery struct {
Type string
ProtagonistType string
CrimeType string
ClueCount int
TotalClues int
RedHerrings int
SuspectCount int
RemainingClues int
Suspect1 string
Motive1 string
Suspect2 string
Motive2 string
Suspect3 string
Motive3 string
}
Romance struct {
Subgenre string
HeatLevel string
MainTrope string
ProtagonistName string
LoveInterestName string
MeetCute string
ConflictType string
ConflictSource string
ResolutionApproach string
}
}
func (input *GenreSpecificGuidance) isProjectZero() bool {
return input.Project.Genre == ""
}
func (input *GenreSpecificGuidance) isFantasyZero() bool {
return input.Fantasy.WorldComplexity == "" &&
input.Fantasy.MagicSystem == "" &&
input.Fantasy.TimePeriod == "" &&
input.Fantasy.Creatures == "" &&
input.Fantasy.Locations == "" &&
input.Fantasy.Conflicts == "" &&
!input.Fantasy.HasMagicRules &&
input.Fantasy.MagicRules == "" &&
input.Fantasy.MagicLimitations == "" &&
input.Fantasy.MagicConsequences == ""
}
func (input *GenreSpecificGuidance) isScifiZero() bool {
return input.Scifi.Subgenre == "" &&
input.Scifi.TechnologyLevel == "" &&
input.Scifi.Setting == "" &&
input.Scifi.Technologies == "" &&
input.Scifi.ScienceFocus == "" &&
input.Scifi.Themes == "" &&
!input.Scifi.HasTimeTravel &&
input.Scifi.TimeTravelType == "" &&
input.Scifi.TimeTravelRules == "" &&
input.Scifi.ParadoxApproach == ""
}
func (input *GenreSpecificGuidance) isMysteryZero() bool {
return input.Mystery.Type == "" &&
input.Mystery.ProtagonistType == "" &&
input.Mystery.CrimeType == "" &&
input.Mystery.ClueCount == 0 &&
input.Mystery.TotalClues == 0 &&
input.Mystery.RedHerrings == 0 &&
input.Mystery.SuspectCount == 0 &&
input.Mystery.RemainingClues == 0 &&
input.Mystery.Suspect1 == "" &&
input.Mystery.Motive1 == "" &&
input.Mystery.Suspect2 == "" &&
input.Mystery.Motive2 == "" &&
input.Mystery.Suspect3 == "" &&
input.Mystery.Motive3 == ""
}
func (input *GenreSpecificGuidance) isRomanceZero() bool {
return input.Romance.Subgenre == "" &&
input.Romance.HeatLevel == "" &&
input.Romance.MainTrope == "" &&
input.Romance.ProtagonistName == "" &&
input.Romance.LoveInterestName == "" &&
input.Romance.MeetCute == "" &&
input.Romance.ConflictType == "" &&
input.Romance.ConflictSource == "" &&
input.Romance.ResolutionApproach == ""
}
func (input *GenreSpecificGuidance) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Mystery.ClueCount)
var1 := strconv.Itoa(input.Mystery.TotalClues)
var2 := strconv.Itoa(input.Mystery.RedHerrings)
var3 := strconv.Itoa(input.Mystery.SuspectCount)
var4 := strconv.Itoa(input.Mystery.RemainingClues)
length := 0
if input.Project.Genre == "fantasy" {
length += 66
length += len(input.Fantasy.WorldComplexity)
length += 19
length += len(input.Fantasy.MagicSystem)
length += 18
length += len(input.Fantasy.TimePeriod)
length += 51
length += len(input.Fantasy.Creatures)
length += 18
length += len(input.Fantasy.Locations)
length += 18
length += len(input.Fantasy.Conflicts)
length += 2
if input.Fantasy.HasMagicRules {
length += 28
length += len(input.Fantasy.MagicRules)
length += 25
length += len(input.Fantasy.MagicLimitations)
length += 19
length += len(input.Fantasy.MagicConsequences)
length += 1
}
length += 150
}
length += 2
if input.Project.Genre == "sci-fi" {
length += 54
length += len(input.Scifi.Subgenre)
length += 17
length += len(input.Scifi.TechnologyLevel)
length += 14
length += len(input.Scifi.Setting)
length += 41
length += len(input.Scifi.Technologies)
length += 28
length += len(input.Scifi.ScienceFocus)
length += 22
length += len(input.Scifi.Themes)
length += 2
if input.Scifi.HasTimeTravel {
length += 41
length += len(input.Scifi.TimeTravelType)
length += 12
length += len(input.Scifi.TimeTravelRules)
length += 23
length += len(input.Scifi.ParadoxApproach)
length += 1
}
length += 138
}
length += 2
if input.Project.Genre == "mystery" {
length += 49
length += len(input.Mystery.Type)
length += 16
length += len(input.Mystery.ProtagonistType)
length += 12
length += len(input.Mystery.CrimeType)
length += 46
length += len(var0)
length += 1
length += len(var1)
length += 21
length += len(var2)
length += 17
length += len(var3)
length += 2
if input.Mystery.ClueCount < input.Mystery.TotalClues {
length += 48
length += len(var4)
length += 13
}
length += 27
length += len(input.Mystery.Suspect1)
length += 11
length += len(input.Mystery.Motive1)
length += 4
length += len(input.Mystery.Suspect2)
length += 11
length += len(input.Mystery.Motive2)
length += 4
length += len(input.Mystery.Suspect3)
length += 11
length += len(input.Mystery.Motive3)
length += 130
}
length += 2
if input.Project.Genre == "romance" {
length += 49
length += len(input.Romance.Subgenre)
length += 17
length += len(input.Romance.HeatLevel)
length += 12
length += len(input.Romance.MainTrope)
length += 48
length += len(input.Romance.ProtagonistName)
length += 22
length += len(input.Romance.LoveInterestName)
length += 18
length += len(input.Romance.MeetCute)
length += 2
if input.Romance.ConflictType != "" {
length += 32
length += len(input.Romance.ConflictType)
length += 13
length += len(input.Romance.ConflictSource)
length += 26
length += len(input.Romance.ResolutionApproach)
length += 1
}
length += 132
}
b.Grow(length)
if input.Project.Genre == "fantasy" {
b.WriteString("\n## ๐งโโ๏ธ Fantasy Writing Mode\n\n**World-Building Focus**: ")
b.WriteString(input.Fantasy.WorldComplexity)
b.WriteString("\n**Magic System**: ")
b.WriteString(input.Fantasy.MagicSystem)
b.WriteString("\n**Setting Era**: ")
b.WriteString(input.Fantasy.TimePeriod)
b.WriteString("\n\n### Fantasy Elements Checklist:\n- **Creatures**: ")
b.WriteString(input.Fantasy.Creatures)
b.WriteString("\n- **Locations**: ")
b.WriteString(input.Fantasy.Locations)
b.WriteString("\n- **Conflicts**: ")
b.WriteString(input.Fantasy.Conflicts)
b.WriteString("\n\n")
if input.Fantasy.HasMagicRules {
b.WriteString("\n### โก Magic System Rules\n")
b.WriteString(input.Fantasy.MagicRules)
b.WriteString("\n\n**Power Limitations**: ")
b.WriteString(input.Fantasy.MagicLimitations)
b.WriteString("\n**Consequences**: ")
b.WriteString(input.Fantasy.MagicConsequences)
b.WriteRune('\n')
}
b.WriteString("\n\n**Writing Tip**: In fantasy, consistency in world-building is key. Make sure your magic system and world rules stay coherent throughout your story.\n")
}
b.WriteString("\n\n")
if input.Project.Genre == "sci-fi" {
b.WriteString("\n## ๐ Science Fiction Writing Mode\n\n**Sub-genre**: ")
b.WriteString(input.Scifi.Subgenre)
b.WriteString("\n**Tech Level**: ")
b.WriteString(input.Scifi.TechnologyLevel)
b.WriteString("\n**Setting**: ")
b.WriteString(input.Scifi.Setting)
b.WriteString("\n\n### Sci-Fi Elements:\n- **Technology**: ")
b.WriteString(input.Scifi.Technologies)
b.WriteString("\n- **Scientific Concepts**: ")
b.WriteString(input.Scifi.ScienceFocus)
b.WriteString("\n- **Social Issues**: ")
b.WriteString(input.Scifi.Themes)
b.WriteString("\n\n")
if input.Scifi.HasTimeTravel {
b.WriteString("\n### โฐ Time Travel Mechanics\n**Type**: ")
b.WriteString(input.Scifi.TimeTravelType)
b.WriteString("\n**Rules**: ")
b.WriteString(input.Scifi.TimeTravelRules)
b.WriteString("\n**Paradox Handling**: ")
b.WriteString(input.Scifi.ParadoxApproach)
b.WriteRune('\n')
}
b.WriteString("\n\n**Writing Tip**: Great sci-fi isn't just about the technologyโit's about how that technology affects human relationships and society.\n")
}
b.WriteString("\n\n")
if input.Project.Genre == "mystery" {
b.WriteString("\n## ๐ Mystery Writing Mode\n\n**Mystery Type**: ")
b.WriteString(input.Mystery.Type)
b.WriteString("\n**Detective**: ")
b.WriteString(input.Mystery.ProtagonistType)
b.WriteString("\n**Crime**: ")
b.WriteString(input.Mystery.CrimeType)
b.WriteString("\n\n### Mystery Structure:\n- **Clues Planted**: ")
b.WriteString(var0)
b.WriteString("/")
b.WriteString(var1)
b.WriteString("\n- **Red Herrings**: ")
b.WriteString(var2)
b.WriteString("\n- **Suspects**: ")
b.WriteString(var3)
b.WriteString("\n\n")
if input.Mystery.ClueCount < input.Mystery.TotalClues {
b.WriteString("\nโ ๏ธ **Clue Alert**: You still need to plant ")
b.WriteString(var4)
b.WriteString(" more clues!\n")
}
b.WriteString("\n\n### Current Suspects:\n1. ")
b.WriteString(input.Mystery.Suspect1)
b.WriteString(" - Motive: ")
b.WriteString(input.Mystery.Motive1)
b.WriteString("\n2. ")
b.WriteString(input.Mystery.Suspect2)
b.WriteString(" - Motive: ")
b.WriteString(input.Mystery.Motive2)
b.WriteString("\n3. ")
b.WriteString(input.Mystery.Suspect3)
b.WriteString(" - Motive: ")
b.WriteString(input.Mystery.Motive3)
b.WriteString("\n\n**Writing Tip**: Play fair with your readersโgive them all the clues they need to solve the mystery alongside your detective.\n")
}
b.WriteString("\n\n")
if input.Project.Genre == "romance" {
b.WriteString("\n## ๐ Romance Writing Mode\n\n**Romance Type**: ")
b.WriteString(input.Romance.Subgenre)
b.WriteString("\n**Heat Level**: ")
b.WriteString(input.Romance.HeatLevel)
b.WriteString("\n**Trope**: ")
b.WriteString(input.Romance.MainTrope)
b.WriteString("\n\n### Character Development:\n- **Protagonist**: ")
b.WriteString(input.Romance.ProtagonistName)
b.WriteString("\n- **Love Interest**: ")
b.WriteString(input.Romance.LoveInterestName)
b.WriteString("\n- **Meet-Cute**: ")
b.WriteString(input.Romance.MeetCute)
b.WriteString("\n\n")
if input.Romance.ConflictType != "" {
b.WriteString("\n### Central Conflict\n**Type**: ")
b.WriteString(input.Romance.ConflictType)
b.WriteString("\n**Source**: ")
b.WriteString(input.Romance.ConflictSource)
b.WriteString("\n**Resolution Strategy**: ")
b.WriteString(input.Romance.ResolutionApproach)
b.WriteRune('\n')
}
b.WriteString("\n\n**Writing Tip**: Great romance is built on emotional truth. Make sure both characters grow and change through their relationship.\n")
}
return b.String()
}
type CharacterDevelopmentWorkshop struct {
Characters struct {
MainCharacter struct {
Name string
Archetype string
Flaw string
Desire string
Arc string
BackstoryComplete bool
NeedsDevelopment bool
WeakAreas string
}
Antagonist struct {
Name string
Type string
Motivation string
Methods string
Sympathetic bool
ConflictStyle string
}
SupportingCast int
Support1 struct {
Name string
Role string
}
Support2 struct {
Name string
Role string
}
Support3 struct {
Name string
Role string
}
}
}
func (input *CharacterDevelopmentWorkshop) isCharactersZero() bool {
return input.isCharactersMainCharacterZero() &&
input.isCharactersAntagonistZero() &&
input.Characters.SupportingCast == 0 &&
input.isCharactersSupport1Zero() &&
input.isCharactersSupport2Zero() &&
input.isCharactersSupport3Zero()
}
func (input *CharacterDevelopmentWorkshop) isCharactersMainCharacterZero() bool {
return input.Characters.MainCharacter.Name == "" &&
input.Characters.MainCharacter.Archetype == "" &&
input.Characters.MainCharacter.Flaw == "" &&
input.Characters.MainCharacter.Desire == "" &&
input.Characters.MainCharacter.Arc == "" &&
!input.Characters.MainCharacter.BackstoryComplete &&
!input.Characters.MainCharacter.NeedsDevelopment &&
input.Characters.MainCharacter.WeakAreas == ""
}
func (input *CharacterDevelopmentWorkshop) isCharactersAntagonistZero() bool {
return input.Characters.Antagonist.Name == "" &&
input.Characters.Antagonist.Type == "" &&
input.Characters.Antagonist.Motivation == "" &&
input.Characters.Antagonist.Methods == "" &&
!input.Characters.Antagonist.Sympathetic &&
input.Characters.Antagonist.ConflictStyle == ""
}
func (input *CharacterDevelopmentWorkshop) isCharactersSupport1Zero() bool {
return input.Characters.Support1.Name == "" &&
input.Characters.Support1.Role == ""
}
func (input *CharacterDevelopmentWorkshop) isCharactersSupport2Zero() bool {
return input.Characters.Support2.Name == "" &&
input.Characters.Support2.Role == ""
}
func (input *CharacterDevelopmentWorkshop) isCharactersSupport3Zero() bool {
return input.Characters.Support3.Name == "" &&
input.Characters.Support3.Role == ""
}
func (input *CharacterDevelopmentWorkshop) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Characters.SupportingCast)
length := 0
length += 28
if !input.isCharactersMainCharacterZero() {
length += 18
length += len(input.Characters.MainCharacter.Name)
length += 17
length += len(input.Characters.MainCharacter.Archetype)
length += 17
length += len(input.Characters.MainCharacter.Flaw)
length += 21
length += len(input.Characters.MainCharacter.Desire)
length += 17
length += len(input.Characters.MainCharacter.Arc)
length += 2
if input.Characters.MainCharacter.BackstoryComplete {
length += 55
}
length += 2
if input.Characters.MainCharacter.NeedsDevelopment {
length += 61
length += len(input.Characters.MainCharacter.WeakAreas)
length += 1
}
length += 1
}
length += 2
if !input.isCharactersAntagonistZero() {
length += 17
length += len(input.Characters.Antagonist.Name)
length += 12
length += len(input.Characters.Antagonist.Type)
length += 17
length += len(input.Characters.Antagonist.Motivation)
length += 14
length += len(input.Characters.Antagonist.Methods)
length += 2
if input.Characters.Antagonist.Sympathetic {
length += 92
}
length += 22
length += len(input.Characters.Antagonist.ConflictStyle)
length += 1
}
length += 2
if input.Characters.SupportingCast != 0 {
length += 28
length += len(var0)
length += 9
if input.Characters.SupportingCast > 5 {
length += 109
}
length += 30
length += len(input.Characters.Support1.Name)
length += 2
length += len(input.Characters.Support1.Role)
length += 3
length += len(input.Characters.Support2.Name)
length += 2
length += len(input.Characters.Support2.Role)
length += 3
length += len(input.Characters.Support3.Name)
length += 2
length += len(input.Characters.Support3.Role)
length += 1
}
b.Grow(length)
b.WriteString("## ๐ฅ Character Analysis\n\n")
if !input.isCharactersMainCharacterZero() {
b.WriteString("\n### Protagonist: ")
b.WriteString(input.Characters.MainCharacter.Name)
b.WriteString("\n\n**Archetype**: ")
b.WriteString(input.Characters.MainCharacter.Archetype)
b.WriteString("\n**Fatal Flaw**: ")
b.WriteString(input.Characters.MainCharacter.Flaw)
b.WriteString("\n**Deepest Desire**: ")
b.WriteString(input.Characters.MainCharacter.Desire)
b.WriteString("\n**Growth Arc**: ")
b.WriteString(input.Characters.MainCharacter.Arc)
b.WriteString("\n\n")
if input.Characters.MainCharacter.BackstoryComplete {
b.WriteString("\nโ
**Backstory Complete**: Well-developed background\n")
}
b.WriteString("\n\n")
if input.Characters.MainCharacter.NeedsDevelopment {
b.WriteString("\n๐ง **Development Needed**: Character needs more depth in: ")
b.WriteString(input.Characters.MainCharacter.WeakAreas)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if !input.isCharactersAntagonistZero() {
b.WriteString("\n### Antagonist: ")
b.WriteString(input.Characters.Antagonist.Name)
b.WriteString("\n\n**Type**: ")
b.WriteString(input.Characters.Antagonist.Type)
b.WriteString("\n**Motivation**: ")
b.WriteString(input.Characters.Antagonist.Motivation)
b.WriteString("\n**Methods**: ")
b.WriteString(input.Characters.Antagonist.Methods)
b.WriteString("\n\n")
if input.Characters.Antagonist.Sympathetic {
b.WriteString("\n๐ก **Sympathetic Villain**: Your antagonist has relatable motivations - this adds depth!\n")
}
b.WriteString("\n\n**Conflict Style**: ")
b.WriteString(input.Characters.Antagonist.ConflictStyle)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Characters.SupportingCast != 0 {
b.WriteString("\n### Supporting Characters (")
b.WriteString(var0)
b.WriteString(" total)\n\n")
if input.Characters.SupportingCast > 5 {
b.WriteString("\nโ ๏ธ **Character Overload**: You have many supporting characters. Make sure each serves a unique purpose!\n")
}
b.WriteString("\n\n**Key Supporting Roles**:\n- ")
b.WriteString(input.Characters.Support1.Name)
b.WriteString(": ")
b.WriteString(input.Characters.Support1.Role)
b.WriteString("\n- ")
b.WriteString(input.Characters.Support2.Name)
b.WriteString(": ")
b.WriteString(input.Characters.Support2.Role)
b.WriteString("\n- ")
b.WriteString(input.Characters.Support3.Name)
b.WriteString(": ")
b.WriteString(input.Characters.Support3.Role)
b.WriteRune('\n')
}
return b.String()
}
type PlotStructureAnalysis struct {
Plot struct {
StructureType string
CurrentAct int
TotalActs int
IncitingIncident bool
FirstPlotPoint bool
Midpoint bool
Climax bool
Resolution bool
Pacing string
TensionLevel int
}
Scene struct {
Description string
Purpose string
PovCharacter string
Location string
PlotGoal string
CharacterGoal string
EmotionalGoal string
HasConflict bool
ConflictType string
}
}
func (input *PlotStructureAnalysis) isPlotZero() bool {
return input.Plot.StructureType == "" &&
input.Plot.CurrentAct == 0 &&
input.Plot.TotalActs == 0 &&
!input.Plot.IncitingIncident &&
!input.Plot.FirstPlotPoint &&
!input.Plot.Midpoint &&
!input.Plot.Climax &&
!input.Plot.Resolution &&
input.Plot.Pacing == "" &&
input.Plot.TensionLevel == 0
}
func (input *PlotStructureAnalysis) isSceneZero() bool {
return input.Scene.Description == "" &&
input.Scene.Purpose == "" &&
input.Scene.PovCharacter == "" &&
input.Scene.Location == "" &&
input.Scene.PlotGoal == "" &&
input.Scene.CharacterGoal == "" &&
input.Scene.EmotionalGoal == "" &&
!input.Scene.HasConflict &&
input.Scene.ConflictType == ""
}
func (input *PlotStructureAnalysis) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Plot.CurrentAct)
var1 := strconv.Itoa(input.Plot.TotalActs)
var2 := strconv.Itoa(input.Plot.TensionLevel)
length := 0
length += 45
length += len(input.Plot.StructureType)
length += 18
length += len(var0)
length += 1
length += len(var1)
length += 29
if input.Plot.IncitingIncident {
length += 38
}
length += 2
if input.Plot.FirstPlotPoint {
length += 39
}
length += 2
if input.Plot.Midpoint {
length += 29
}
length += 2
if input.Plot.Climax {
length += 27
}
length += 2
if input.Plot.Resolution {
length += 31
}
length += 40
length += len(input.Plot.Pacing)
length += 20
length += len(var2)
length += 5
if input.Plot.TensionLevel < 4 {
length += 84
}
length += 2
if input.Plot.TensionLevel > 8 {
length += 93
}
length += 42
length += len(input.Scene.Description)
length += 14
length += len(input.Scene.Purpose)
length += 20
length += len(input.Scene.PovCharacter)
length += 14
length += len(input.Scene.Location)
length += 49
length += len(input.Scene.PlotGoal)
length += 31
length += len(input.Scene.CharacterGoal)
length += 24
length += len(input.Scene.EmotionalGoal)
length += 2
if input.Scene.HasConflict {
length += 79
length += len(input.Scene.ConflictType)
length += 1
}
b.Grow(length)
b.WriteString("## ๐ Story Structure\n\n**Structure Type**: ")
b.WriteString(input.Plot.StructureType)
b.WriteString("\n**Current Act**: ")
b.WriteString(var0)
b.WriteString("/")
b.WriteString(var1)
b.WriteString("\n\n### Plot Points Checklist:\n")
if input.Plot.IncitingIncident {
b.WriteString("\nโ
**Inciting Incident**: Completed\n")
}
b.WriteString("\n\n")
if input.Plot.FirstPlotPoint {
b.WriteString("\nโ
**First Plot Point**: Completed \n")
}
b.WriteString("\n\n")
if input.Plot.Midpoint {
b.WriteString("\nโ
**Midpoint**: Completed\n")
}
b.WriteString("\n\n")
if input.Plot.Climax {
b.WriteString("\nโ
**Climax**: Completed\n")
}
b.WriteString("\n\n")
if input.Plot.Resolution {
b.WriteString("\nโ
**Resolution**: Completed\n")
}
b.WriteString("\n\n### Pacing Analysis\n**Current Pace**: ")
b.WriteString(input.Plot.Pacing)
b.WriteString("\n**Tension Level**: ")
b.WriteString(var2)
b.WriteString("/10\n\n")
if input.Plot.TensionLevel < 4 {
b.WriteString("\n๐ **Pacing Note**: Consider adding more conflict or stakes to increase tension.\n")
}
b.WriteString("\n\n")
if input.Plot.TensionLevel > 8 {
b.WriteString("\nโ ๏ธ **High Intensity**: Make sure to give readers breathing room between intense scenes.\n")
}
b.WriteString("\n\n## ๐ฏ Scene Goals\n\n**Today's Scene**: ")
b.WriteString(input.Scene.Description)
b.WriteString("\n**Purpose**: ")
b.WriteString(input.Scene.Purpose)
b.WriteString("\n**POV Character**: ")
b.WriteString(input.Scene.PovCharacter)
b.WriteString("\n**Setting**: ")
b.WriteString(input.Scene.Location)
b.WriteString("\n\n### Scene Objectives:\n1. **Plot Advancement**: ")
b.WriteString(input.Scene.PlotGoal)
b.WriteString("\n2. **Character Development**: ")
b.WriteString(input.Scene.CharacterGoal)
b.WriteString("\n3. **Emotional Beat**: ")
b.WriteString(input.Scene.EmotionalGoal)
b.WriteString("\n\n")
if input.Scene.HasConflict {
b.WriteString("\nโ
**Conflict Present**: Good! Every scene needs tension.\n**Conflict Type**: ")
b.WriteString(input.Scene.ConflictType)
b.WriteRune('\n')
}
return b.String()
}
type WritingProductivityDashboard struct {
Session struct {
DailyGoal int
WordsWritten int
ProgressPercentage int
BonusWords int
StartTime string
EndTime string
NextGoal string
}
Motivation struct {
Tip string
}
Stats struct {
WordsPerMinute int
SessionMinutes int
BreakMinutes int
WritingSpeedTrend string
}
Prompts struct {
HasCustom bool
CustomPrompt string
CustomReason string
GeneralSuggestions string
CharacterExercise string
DialogueChallenge string
WorldbuildingPrompt string
}
Tools struct {
GrammarCheck bool
ResearchNeeded bool
ResearchTopics string
}
Writer struct {
Experience string
Name string
}
Tips struct {
BeginnerFocus string
AdvancedChallenge string
}
}
func (input *WritingProductivityDashboard) isSessionZero() bool {
return input.Session.DailyGoal == 0 &&
input.Session.WordsWritten == 0 &&
input.Session.ProgressPercentage == 0 &&
input.Session.BonusWords == 0 &&
input.Session.StartTime == "" &&
input.Session.EndTime == "" &&
input.Session.NextGoal == ""
}
func (input *WritingProductivityDashboard) isMotivationZero() bool {
return input.Motivation.Tip == ""
}
func (input *WritingProductivityDashboard) isStatsZero() bool {
return input.Stats.WordsPerMinute == 0 &&
input.Stats.SessionMinutes == 0 &&
input.Stats.BreakMinutes == 0 &&
input.Stats.WritingSpeedTrend == ""
}
func (input *WritingProductivityDashboard) isPromptsZero() bool {
return !input.Prompts.HasCustom &&
input.Prompts.CustomPrompt == "" &&
input.Prompts.CustomReason == "" &&
input.Prompts.GeneralSuggestions == "" &&
input.Prompts.CharacterExercise == "" &&
input.Prompts.DialogueChallenge == "" &&
input.Prompts.WorldbuildingPrompt == ""
}
func (input *WritingProductivityDashboard) isToolsZero() bool {
return !input.Tools.GrammarCheck &&
!input.Tools.ResearchNeeded &&
input.Tools.ResearchTopics == ""
}
func (input *WritingProductivityDashboard) isWriterZero() bool {
return input.Writer.Experience == "" &&
input.Writer.Name == ""
}
func (input *WritingProductivityDashboard) isTipsZero() bool {
return input.Tips.BeginnerFocus == "" &&
input.Tips.AdvancedChallenge == ""
}
func (input *WritingProductivityDashboard) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Session.DailyGoal)
var1 := strconv.Itoa(input.Session.WordsWritten)
var2 := strconv.Itoa(input.Session.ProgressPercentage)
var3 := strconv.Itoa(input.Session.BonusWords)
var4 := strconv.Itoa(input.Stats.WordsPerMinute)
var5 := strconv.Itoa(input.Stats.SessionMinutes)
var6 := strconv.Itoa(input.Stats.BreakMinutes)
length := 0
length += 43
length += len(var0)
length += 29
length += len(var1)
length += 8
length += len(var2)
length += 4
if input.Session.ProgressPercentage >= 100 {
length += 48
if input.Session.BonusWords != 0 {
length += 19
length += len(var3)
length += 23
}
length += 1
}
length += 2
if input.Session.ProgressPercentage < 50 {
length += 75
length += len(input.Motivation.Tip)
length += 1
}
length += 47
length += len(var4)
length += 38
length += len(var5)
length += 27
length += len(var6)
length += 10
if input.Stats.WritingSpeedTrend != "" {
length += 18
length += len(input.Stats.WritingSpeedTrend)
length += 25
}
length += 41
if input.Prompts.HasCustom {
length += 34
length += len(input.Prompts.CustomPrompt)
length += 32
length += len(input.Prompts.CustomReason)
length += 1
}
length += 2
if input.Prompts.GeneralSuggestions != "" {
length += 58
length += len(input.Prompts.CharacterExercise)
length += 27
length += len(input.Prompts.DialogueChallenge)
length += 23
length += len(input.Prompts.WorldbuildingPrompt)
length += 1
}
length += 32
if input.Tools.GrammarCheck {
length += 90
}
length += 2
if input.Tools.ResearchNeeded {
length += 55
length += len(input.Tools.ResearchTopics)
length += 1
}
length += 2
if input.Writer.Experience == "beginner" {
length += 203
length += len(input.Tips.BeginnerFocus)
length += 1
}
length += 2
if input.Writer.Experience == "advanced" {
length += 170
length += len(input.Tips.AdvancedChallenge)
length += 1
}
length += 24
length += len(input.Session.StartTime)
length += 3
length += len(input.Session.EndTime)
length += 24
length += len(input.Session.NextGoal)
length += 17
length += len(input.Writer.Name)
length += 45
b.Grow(length)
b.WriteString("## ๐ Writing Metrics\n\n**Today's Goal**: ")
b.WriteString(var0)
b.WriteString(" words\n**Current Progress**: ")
b.WriteString(var1)
b.WriteString(" words (")
b.WriteString(var2)
b.WriteString("%)\n\n")
if input.Session.ProgressPercentage >= 100 {
b.WriteString("\n๐ **Goal Achieved!** Fantastic work today!\n\n")
if input.Session.BonusWords != 0 {
b.WriteString("\n**Bonus Words**: +")
b.WriteString(var3)
b.WriteString(" words over your goal!\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Session.ProgressPercentage < 50 {
b.WriteString("\n๐ช **Keep Going**: You're halfway to your goal! \n\n**Motivational Tip**: ")
b.WriteString(input.Motivation.Tip)
b.WriteRune('\n')
}
b.WriteString("\n\n### Writing Statistics:\n- **Writing Speed**: ")
b.WriteString(var4)
b.WriteString(" words/minute\n- **Session Duration**: ")
b.WriteString(var5)
b.WriteString(" minutes\n- **Break Time**: ")
b.WriteString(var6)
b.WriteString(" minutes\n\n")
if input.Stats.WritingSpeedTrend != "" {
b.WriteString("\n**Speed Trend**: ")
b.WriteString(input.Stats.WritingSpeedTrend)
b.WriteString(" (compared to last week)\n")
}
b.WriteString("\n\n## ๐จ Writing Prompts & Inspiration\n\n")
if input.Prompts.HasCustom {
b.WriteString("\n### Personalized Prompt for You:\n")
b.WriteString(input.Prompts.CustomPrompt)
b.WriteString("\n\n**Why this fits your style**: ")
b.WriteString(input.Prompts.CustomReason)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Prompts.GeneralSuggestions != "" {
b.WriteString("\n### General Writing Exercises:\n- **Character Exercise**: ")
b.WriteString(input.Prompts.CharacterExercise)
b.WriteString("\n- **Dialogue Challenge**: ")
b.WriteString(input.Prompts.DialogueChallenge)
b.WriteString("\n- **World-Building**: ")
b.WriteString(input.Prompts.WorldbuildingPrompt)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ง Writing Tools & Tips\n\n")
if input.Tools.GrammarCheck {
b.WriteString("\n๐ **Grammar Assistant**: Available - Run a quick check before finishing your session.\n")
}
b.WriteString("\n\n")
if input.Tools.ResearchNeeded {
b.WriteString("\n๐ **Research Reminder**: Don't forget to research: ")
b.WriteString(input.Tools.ResearchTopics)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Writer.Experience == "beginner" {
b.WriteString("\n### Beginner Writer Tips:\n- Write first, edit later\n- Don't worry about perfection in your first draft\n- Read extensively in your chosen genre\n- Join a writing community for support\n\n**Today's Focus**: ")
b.WriteString(input.Tips.BeginnerFocus)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Writer.Experience == "advanced" {
b.WriteString("\n### Advanced Techniques:\n- Experiment with unreliable narrators\n- Layer multiple subplots effectively\n- Master show vs. tell\n- Develop your unique voice\n\n**Challenge**: ")
b.WriteString(input.Tips.AdvancedChallenge)
b.WriteRune('\n')
}
b.WriteString("\n\n---\n**Session Time**: ")
b.WriteString(input.Session.StartTime)
b.WriteString(" - ")
b.WriteString(input.Session.EndTime)
b.WriteString("\n**Next Session Goal**: ")
b.WriteString(input.Session.NextGoal)
b.WriteString("\n\n*Keep writing, ")
b.WriteString(input.Writer.Name)
b.WriteString("! Your story is waiting to be told. ๐โจ* ")
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package customersupport
import (
"strconv"
"strings"
)
type SystemPrompt struct{}
func (input *SystemPrompt) String() string {
return "You are SupportBot Pro, an intelligent customer support agent with advanced escalation capabilities. You provide empathetic, efficient support while routing complex issues to appropriate specialists based on priority, customer tier, and issue complexity."
}
type InitialCustomerGreeting struct {
Ticket struct {
Id string
}
Customer struct {
Name string
Tier string
JoinDate string
TicketHistory struct {
TotalCount int
}
SatisfactionScore int
AccountManager string
ManagerContact string
}
Issue struct {
Category string
Severity string
PriorityLevel int
Timestamp string
IsUrgent bool
}
}
func (input *InitialCustomerGreeting) isTicketZero() bool {
return input.Ticket.Id == ""
}
func (input *InitialCustomerGreeting) isCustomerZero() bool {
return input.Customer.Name == "" &&
input.Customer.Tier == "" &&
input.Customer.JoinDate == "" &&
input.isCustomerTicketHistoryZero() &&
input.Customer.SatisfactionScore == 0 &&
input.Customer.AccountManager == "" &&
input.Customer.ManagerContact == ""
}
func (input *InitialCustomerGreeting) isCustomerTicketHistoryZero() bool {
return input.Customer.TicketHistory.TotalCount == 0
}
func (input *InitialCustomerGreeting) isIssueZero() bool {
return input.Issue.Category == "" &&
input.Issue.Severity == "" &&
input.Issue.PriorityLevel == 0 &&
input.Issue.Timestamp == "" &&
!input.Issue.IsUrgent
}
func (input *InitialCustomerGreeting) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Customer.TicketHistory.TotalCount)
var1 := strconv.Itoa(input.Customer.SatisfactionScore)
var2 := strconv.Itoa(input.Issue.PriorityLevel)
length := 0
length += 34
length += len(input.Ticket.Id)
length += 8
length += len(input.Customer.Name)
length += 76
length += len(input.Customer.Tier)
length += 30
length += len(input.Customer.JoinDate)
length += 25
length += len(var0)
length += 27
length += len(var1)
length += 9
if input.Customer.Tier == "premium" {
length += 95
}
length += 2
if input.Customer.Tier == "enterprise" {
length += 64
length += len(input.Customer.AccountManager)
length += 15
length += len(input.Customer.ManagerContact)
length += 2
}
length += 40
length += len(input.Issue.Category)
length += 17
length += len(input.Issue.Severity)
length += 17
length += len(var2)
length += 19
length += len(input.Issue.Timestamp)
length += 2
if input.Issue.IsUrgent {
length += 85
}
b.Grow(length)
b.WriteString("# ๐ง Customer Support - Ticket #")
b.WriteString(input.Ticket.Id)
b.WriteString("\n\nHello ")
b.WriteString(input.Customer.Name)
b.WriteString("! I'm here to help you today.\n\n## ๐ค Customer Profile\n- **Account Type**: ")
b.WriteString(input.Customer.Tier)
b.WriteString(" Customer\n- **Member Since**: ")
b.WriteString(input.Customer.JoinDate)
b.WriteString("\n- **Previous Tickets**: ")
b.WriteString(var0)
b.WriteString("\n- **Satisfaction Score**: ")
b.WriteString(var1)
b.WriteString("/10 โญ\n\n")
if input.Customer.Tier == "premium" {
b.WriteString("\n๐ **Premium Support**: You'll receive priority assistance with our fastest response times.\n")
}
b.WriteString("\n\n")
if input.Customer.Tier == "enterprise" {
b.WriteString("\n๐ข **Enterprise Support**: Your dedicated account manager is ")
b.WriteString(input.Customer.AccountManager)
b.WriteString(", available at ")
b.WriteString(input.Customer.ManagerContact)
b.WriteString(".\n")
}
b.WriteString("\n\n## ๐ Issue Details\n- **Category**: ")
b.WriteString(input.Issue.Category)
b.WriteString("\n- **Severity**: ")
b.WriteString(input.Issue.Severity)
b.WriteString("\n- **Priority**: ")
b.WriteString(var2)
b.WriteString("/5\n- **Reported**: ")
b.WriteString(input.Issue.Timestamp)
b.WriteString("\n\n")
if input.Issue.IsUrgent {
b.WriteString("\n๐จ **URGENT ISSUE DETECTED** - This will be fast-tracked for immediate attention.\n")
}
return b.String()
}
type IssueAnalysisAndRouting struct {
Issue struct {
Description string
Category string
}
Technical struct {
System string
ErrorCode string
Environment string
HasLogs bool
Logs string
ComplexityScore int
EstimatedHours int
AssignedSpecialist string
EstimatedMinutes int
}
Billing struct {
InvoiceId string
Amount float64
PaymentMethod string
DisputeReason string
RequiresRefund bool
RefundAmount float64
RefundDays int
PaymentFailed bool
FailureReason string
NextRetryDate string
CustomerAction string
}
Customer struct {
Tier string
}
Account struct {
IssueType string
Status string
Locked bool
LockReason string
SecurityLevel string
UnlockStep1 string
UnlockStep2 string
UnlockStep3 string
RequiresVerification bool
VerificationDocuments string
SubscriptionChange bool
CurrentPlan string
RequestedPlan string
ChangeDate string
ProrateAmount float64
}
}
func (input *IssueAnalysisAndRouting) isIssueZero() bool {
return input.Issue.Description == "" &&
input.Issue.Category == ""
}
func (input *IssueAnalysisAndRouting) isTechnicalZero() bool {
return input.Technical.System == "" &&
input.Technical.ErrorCode == "" &&
input.Technical.Environment == "" &&
!input.Technical.HasLogs &&
input.Technical.Logs == "" &&
input.Technical.ComplexityScore == 0 &&
input.Technical.EstimatedHours == 0 &&
input.Technical.AssignedSpecialist == "" &&
input.Technical.EstimatedMinutes == 0
}
func (input *IssueAnalysisAndRouting) isBillingZero() bool {
return input.Billing.InvoiceId == "" &&
input.Billing.Amount == 0 &&
input.Billing.PaymentMethod == "" &&
input.Billing.DisputeReason == "" &&
!input.Billing.RequiresRefund &&
input.Billing.RefundAmount == 0 &&
input.Billing.RefundDays == 0 &&
!input.Billing.PaymentFailed &&
input.Billing.FailureReason == "" &&
input.Billing.NextRetryDate == "" &&
input.Billing.CustomerAction == ""
}
func (input *IssueAnalysisAndRouting) isCustomerZero() bool {
return input.Customer.Tier == ""
}
func (input *IssueAnalysisAndRouting) isAccountZero() bool {
return input.Account.IssueType == "" &&
input.Account.Status == "" &&
!input.Account.Locked &&
input.Account.LockReason == "" &&
input.Account.SecurityLevel == "" &&
input.Account.UnlockStep1 == "" &&
input.Account.UnlockStep2 == "" &&
input.Account.UnlockStep3 == "" &&
!input.Account.RequiresVerification &&
input.Account.VerificationDocuments == "" &&
!input.Account.SubscriptionChange &&
input.Account.CurrentPlan == "" &&
input.Account.RequestedPlan == "" &&
input.Account.ChangeDate == "" &&
input.Account.ProrateAmount == 0
}
func (input *IssueAnalysisAndRouting) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Technical.ComplexityScore)
var1 := strconv.Itoa(input.Technical.EstimatedHours)
var2 := strconv.Itoa(input.Technical.EstimatedMinutes)
var3 := strconv.FormatFloat(input.Billing.Amount, 'g', -1, 64)
var4 := strconv.FormatFloat(input.Billing.RefundAmount, 'g', -1, 64)
var5 := strconv.Itoa(input.Billing.RefundDays)
var6 := strconv.FormatFloat(input.Account.ProrateAmount, 'g', -1, 64)
length := 0
length += 47
length += len(input.Issue.Description)
length += 2
if input.Issue.Category == "technical" {
length += 60
length += len(input.Technical.System)
length += 17
length += len(input.Technical.ErrorCode)
length += 18
length += len(input.Technical.Environment)
length += 2
if input.Technical.HasLogs {
length += 35
length += len(input.Technical.Logs)
length += 5
}
length += 2
if input.Technical.ComplexityScore != 0 {
length += 28
length += len(var0)
length += 5
if input.Technical.ComplexityScore >= 8 {
length += 90
length += len(var1)
length += 32
length += len(input.Technical.AssignedSpecialist)
length += 1
}
length += 2
if input.Technical.ComplexityScore <= 3 {
length += 93
length += len(var2)
length += 9
}
length += 1
}
length += 1
}
length += 2
if input.Issue.Category == "billing" {
length += 54
length += len(input.Billing.InvoiceId)
length += 14
length += len(var3)
length += 21
length += len(input.Billing.PaymentMethod)
length += 21
length += len(input.Billing.DisputeReason)
length += 2
if input.Billing.RequiresRefund {
length += 54
length += len(var4)
length += 22
length += len(var5)
length += 16
if input.Customer.Tier == "premium" {
length += 75
}
length += 2
if input.Customer.Tier == "enterprise" {
length += 75
}
length += 1
}
length += 2
if input.Billing.PaymentFailed {
length += 54
length += len(input.Billing.FailureReason)
length += 17
length += len(input.Billing.NextRetryDate)
length += 23
length += len(input.Billing.CustomerAction)
length += 1
}
length += 1
}
length += 2
if input.Issue.Category == "account" {
length += 50
length += len(input.Account.IssueType)
length += 21
length += len(input.Account.Status)
length += 2
if input.Account.Locked {
length += 42
length += len(input.Account.LockReason)
length += 21
length += len(input.Account.SecurityLevel)
length += 23
length += len(input.Account.UnlockStep1)
length += 4
length += len(input.Account.UnlockStep2)
length += 4
length += len(input.Account.UnlockStep3)
length += 2
if input.Account.RequiresVerification {
length += 52
length += len(input.Account.VerificationDocuments)
length += 1
}
length += 1
}
length += 2
if input.Account.SubscriptionChange {
length += 56
length += len(input.Account.CurrentPlan)
length += 21
length += len(input.Account.RequestedPlan)
length += 21
length += len(input.Account.ChangeDate)
length += 23
length += len(var6)
length += 1
}
length += 1
}
b.Grow(length)
b.WriteString("## ๐ Issue Classification\n\n**Description**: ")
b.WriteString(input.Issue.Description)
b.WriteString("\n\n")
if input.Issue.Category == "technical" {
b.WriteString("\n### ๐ ๏ธ Technical Issue Analysis\n\n**System Affected**: ")
b.WriteString(input.Technical.System)
b.WriteString("\n**Error Code**: ")
b.WriteString(input.Technical.ErrorCode)
b.WriteString("\n**Environment**: ")
b.WriteString(input.Technical.Environment)
b.WriteString("\n\n")
if input.Technical.HasLogs {
b.WriteString("\n**Error Logs Available**: Yes\n```\n")
b.WriteString(input.Technical.Logs)
b.WriteString("\n```\n")
}
b.WriteString("\n\n")
if input.Technical.ComplexityScore != 0 {
b.WriteString("\n**Complexity Assessment**: ")
b.WriteString(var0)
b.WriteString("/10\n\n")
if input.Technical.ComplexityScore >= 8 {
b.WriteString("\n๐ด **High Complexity** - Escalating to Senior Technical Team\n**Estimated Resolution**: ")
b.WriteString(var1)
b.WriteString(" hours\n**Assigned Specialist**: ")
b.WriteString(input.Technical.AssignedSpecialist)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Technical.ComplexityScore <= 3 {
b.WriteString("\nโ
**Standard Issue** - Can be resolved with standard procedures\n**Estimated Resolution**: ")
b.WriteString(var2)
b.WriteString(" minutes\n")
}
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Issue.Category == "billing" {
b.WriteString("\n### ๐ณ Billing Issue Analysis\n\n**Invoice Number**: ")
b.WriteString(input.Billing.InvoiceId)
b.WriteString("\n**Amount**: $")
b.WriteString(var3)
b.WriteString("\n**Payment Method**: ")
b.WriteString(input.Billing.PaymentMethod)
b.WriteString("\n**Dispute Reason**: ")
b.WriteString(input.Billing.DisputeReason)
b.WriteString("\n\n")
if input.Billing.RequiresRefund {
b.WriteString("\n๐ฐ **Refund Request Detected**\n**Refund Amount**: $")
b.WriteString(var4)
b.WriteString("\n**Processing Time**: ")
b.WriteString(var5)
b.WriteString(" business days\n\n")
if input.Customer.Tier == "premium" {
b.WriteString("\nโก **Fast-Track Refund**: Your refund will be processed within 24 hours.\n")
}
b.WriteString("\n\n")
if input.Customer.Tier == "enterprise" {
b.WriteString("\nโก **Fast-Track Refund**: Your refund will be processed within 24 hours.\n")
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Billing.PaymentFailed {
b.WriteString("\nโ **Payment Failure Detected**\n**Failure Reason**: ")
b.WriteString(input.Billing.FailureReason)
b.WriteString("\n**Next Retry**: ")
b.WriteString(input.Billing.NextRetryDate)
b.WriteString("\n\n**Action Required**: ")
b.WriteString(input.Billing.CustomerAction)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Issue.Category == "account" {
b.WriteString("\n### ๐ค Account Issue Analysis\n\n**Issue Type**: ")
b.WriteString(input.Account.IssueType)
b.WriteString("\n**Account Status**: ")
b.WriteString(input.Account.Status)
b.WriteString("\n\n")
if input.Account.Locked {
b.WriteString("\n๐ **Account Locked**\n**Lock Reason**: ")
b.WriteString(input.Account.LockReason)
b.WriteString("\n**Security Level**: ")
b.WriteString(input.Account.SecurityLevel)
b.WriteString("\n\n**Unlock Steps**:\n1. ")
b.WriteString(input.Account.UnlockStep1)
b.WriteString("\n2. ")
b.WriteString(input.Account.UnlockStep2)
b.WriteString("\n3. ")
b.WriteString(input.Account.UnlockStep3)
b.WriteString("\n\n")
if input.Account.RequiresVerification {
b.WriteString("\n**Identity Verification Required**: Please prepare ")
b.WriteString(input.Account.VerificationDocuments)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Account.SubscriptionChange {
b.WriteString("\n๐ **Subscription Change Request**\n**Current Plan**: ")
b.WriteString(input.Account.CurrentPlan)
b.WriteString("\n**Requested Plan**: ")
b.WriteString(input.Account.RequestedPlan)
b.WriteString("\n**Effective Date**: ")
b.WriteString(input.Account.ChangeDate)
b.WriteString("\n**Prorated Amount**: $")
b.WriteString(var6)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
return b.String()
}
type IntelligentEscalationLogic struct {
Escalation struct {
Required bool
Level int
Reason string
ManagerName string
ManagerContact string
SlaHours int
ExecutiveContact string
Notes string
SpecialistTeam string
ExpertName string
ExpertSpecialty string
ResponseTime int
SeniorAgent string
QueuePosition int
PickupTime string
CallbackRequested bool
CallbackTime string
}
Customer struct {
VipStatus bool
Phone string
Timezone string
}
}
func (input *IntelligentEscalationLogic) isEscalationZero() bool {
return !input.Escalation.Required &&
input.Escalation.Level == 0 &&
input.Escalation.Reason == "" &&
input.Escalation.ManagerName == "" &&
input.Escalation.ManagerContact == "" &&
input.Escalation.SlaHours == 0 &&
input.Escalation.ExecutiveContact == "" &&
input.Escalation.Notes == "" &&
input.Escalation.SpecialistTeam == "" &&
input.Escalation.ExpertName == "" &&
input.Escalation.ExpertSpecialty == "" &&
input.Escalation.ResponseTime == 0 &&
input.Escalation.SeniorAgent == "" &&
input.Escalation.QueuePosition == 0 &&
input.Escalation.PickupTime == "" &&
!input.Escalation.CallbackRequested &&
input.Escalation.CallbackTime == ""
}
func (input *IntelligentEscalationLogic) isCustomerZero() bool {
return !input.Customer.VipStatus &&
input.Customer.Phone == "" &&
input.Customer.Timezone == ""
}
func (input *IntelligentEscalationLogic) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Escalation.Level)
var1 := strconv.Itoa(input.Escalation.SlaHours)
var2 := strconv.Itoa(input.Escalation.ResponseTime)
var3 := strconv.Itoa(input.Escalation.QueuePosition)
length := 0
if input.Escalation.Required {
length += 53
length += len(var0)
length += 21
length += len(input.Escalation.Reason)
length += 2
if input.Escalation.Level >= 3 {
length += 74
length += len(input.Escalation.ManagerName)
length += 14
length += len(input.Escalation.ManagerContact)
length += 10
length += len(var1)
length += 8
if input.Customer.VipStatus {
length += 87
length += len(input.Escalation.ExecutiveContact)
length += 1
}
length += 24
length += len(input.Escalation.Notes)
length += 1
}
length += 2
if input.Escalation.Level == 2 {
length += 73
length += len(input.Escalation.SpecialistTeam)
length += 22
length += len(input.Escalation.ExpertName)
length += 16
length += len(input.Escalation.ExpertSpecialty)
length += 25
length += len(var2)
length += 9
}
length += 2
if input.Escalation.Level == 1 {
length += 65
length += len(input.Escalation.SeniorAgent)
length += 21
length += len(var3)
length += 22
length += len(input.Escalation.PickupTime)
length += 1
}
length += 2
if input.Escalation.CallbackRequested {
length += 47
length += len(input.Customer.Phone)
length += 21
length += len(input.Escalation.CallbackTime)
length += 16
length += len(input.Customer.Timezone)
length += 1
}
length += 1
}
b.Grow(length)
if input.Escalation.Required {
b.WriteString("\n## ๐ Escalation Triggered\n\n**Escalation Level**: ")
b.WriteString(var0)
b.WriteString("\n**Trigger Reason**: ")
b.WriteString(input.Escalation.Reason)
b.WriteString("\n\n")
if input.Escalation.Level >= 3 {
b.WriteString("\n### ๐ด Level 3+ Escalation - Management Involvement\n\n**Escalated To**: ")
b.WriteString(input.Escalation.ManagerName)
b.WriteString("\n**Contact**: ")
b.WriteString(input.Escalation.ManagerContact)
b.WriteString("\n**SLA**: ")
b.WriteString(var1)
b.WriteString(" hours\n\n")
if input.Customer.VipStatus {
b.WriteString("\n๐ **VIP Customer Alert**: Executive team has been notified.\n**Executive Contact**: ")
b.WriteString(input.Escalation.ExecutiveContact)
b.WriteRune('\n')
}
b.WriteString("\n\n**Escalation Notes**: ")
b.WriteString(input.Escalation.Notes)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Escalation.Level == 2 {
b.WriteString("\n### ๐ก Level 2 Escalation - Specialist Required\n\n**Specialist Team**: ")
b.WriteString(input.Escalation.SpecialistTeam)
b.WriteString("\n**Expert Assigned**: ")
b.WriteString(input.Escalation.ExpertName)
b.WriteString("\n**Specialty**: ")
b.WriteString(input.Escalation.ExpertSpecialty)
b.WriteString("\n**Estimated Response**: ")
b.WriteString(var2)
b.WriteString(" minutes\n")
}
b.WriteString("\n\n")
if input.Escalation.Level == 1 {
b.WriteString("\n### ๐ข Level 1 Escalation - Senior Support\n\n**Senior Agent**: ")
b.WriteString(input.Escalation.SeniorAgent)
b.WriteString("\n**Queue Position**: ")
b.WriteString(var3)
b.WriteString("\n**Expected Pickup**: ")
b.WriteString(input.Escalation.PickupTime)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Escalation.CallbackRequested {
b.WriteString("\n๐ **Callback Scheduled**\n**Phone Number**: ")
b.WriteString(input.Customer.Phone)
b.WriteString("\n**Preferred Time**: ")
b.WriteString(input.Escalation.CallbackTime)
b.WriteString("\n**Time Zone**: ")
b.WriteString(input.Customer.Timezone)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
return b.String()
}
type ResolutionAndNextSteps struct {
Solution struct {
HasQuickFix bool
QuickFix string
Step1 string
Step2 string
Step3 string
RequiresRestart bool
RestartComponent string
VerificationSteps string
RequiresInvestigation bool
}
Investigation struct {
Timeline string
Team string
UpdateFrequency int
Check1 string
Check2 string
Check3 string
ContactMethod string
}
Ticket struct {
Status string
PriorityLevel string
NextUpdate string
SlaAtRisk bool
SlaDeadline string
Id string
}
Followup struct {
SurveyLink string
HasCompensation bool
CompensationType string
CompensationValue float64
CompensationTarget string
CompensationDate string
}
Customer struct {
Tier string
DedicatedSupportContact string
EmergencyContact string
Name string
}
Support struct {
PremiumPhone string
PremiumWaitTime int
GeneralContact string
ChatUrl string
KbUrl string
}
Agent struct {
Name string
Id string
}
Session struct {
EndTime string
}
}
func (input *ResolutionAndNextSteps) isSolutionZero() bool {
return !input.Solution.HasQuickFix &&
input.Solution.QuickFix == "" &&
input.Solution.Step1 == "" &&
input.Solution.Step2 == "" &&
input.Solution.Step3 == "" &&
!input.Solution.RequiresRestart &&
input.Solution.RestartComponent == "" &&
input.Solution.VerificationSteps == "" &&
!input.Solution.RequiresInvestigation
}
func (input *ResolutionAndNextSteps) isInvestigationZero() bool {
return input.Investigation.Timeline == "" &&
input.Investigation.Team == "" &&
input.Investigation.UpdateFrequency == 0 &&
input.Investigation.Check1 == "" &&
input.Investigation.Check2 == "" &&
input.Investigation.Check3 == "" &&
input.Investigation.ContactMethod == ""
}
func (input *ResolutionAndNextSteps) isTicketZero() bool {
return input.Ticket.Status == "" &&
input.Ticket.PriorityLevel == "" &&
input.Ticket.NextUpdate == "" &&
!input.Ticket.SlaAtRisk &&
input.Ticket.SlaDeadline == "" &&
input.Ticket.Id == ""
}
func (input *ResolutionAndNextSteps) isFollowupZero() bool {
return input.Followup.SurveyLink == "" &&
!input.Followup.HasCompensation &&
input.Followup.CompensationType == "" &&
input.Followup.CompensationValue == 0 &&
input.Followup.CompensationTarget == "" &&
input.Followup.CompensationDate == ""
}
func (input *ResolutionAndNextSteps) isCustomerZero() bool {
return input.Customer.Tier == "" &&
input.Customer.DedicatedSupportContact == "" &&
input.Customer.EmergencyContact == "" &&
input.Customer.Name == ""
}
func (input *ResolutionAndNextSteps) isSupportZero() bool {
return input.Support.PremiumPhone == "" &&
input.Support.PremiumWaitTime == 0 &&
input.Support.GeneralContact == "" &&
input.Support.ChatUrl == "" &&
input.Support.KbUrl == ""
}
func (input *ResolutionAndNextSteps) isAgentZero() bool {
return input.Agent.Name == "" &&
input.Agent.Id == ""
}
func (input *ResolutionAndNextSteps) isSessionZero() bool {
return input.Session.EndTime == ""
}
func (input *ResolutionAndNextSteps) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Investigation.UpdateFrequency)
var1 := strconv.FormatFloat(input.Followup.CompensationValue, 'g', -1, 64)
var2 := strconv.Itoa(input.Support.PremiumWaitTime)
length := 0
length += 27
if input.Solution.HasQuickFix {
length += 51
length += len(input.Solution.QuickFix)
length += 27
length += len(input.Solution.Step1)
length += 4
length += len(input.Solution.Step2)
length += 4
length += len(input.Solution.Step3)
length += 2
if input.Solution.RequiresRestart {
length += 52
length += len(input.Solution.RestartComponent)
length += 35
}
length += 20
length += len(input.Solution.VerificationSteps)
length += 1
}
length += 2
if input.Solution.RequiresInvestigation {
length += 68
length += len(input.Investigation.Timeline)
length += 20
length += len(input.Investigation.Team)
length += 29
length += len(var0)
length += 35
length += len(input.Investigation.Check1)
length += 3
length += len(input.Investigation.Check2)
length += 3
length += len(input.Investigation.Check3)
length += 29
length += len(input.Investigation.ContactMethod)
length += 1
}
length += 52
length += len(input.Ticket.Status)
length += 15
length += len(input.Ticket.PriorityLevel)
length += 20
length += len(input.Ticket.NextUpdate)
length += 2
if input.Ticket.SlaAtRisk {
length += 64
length += len(input.Ticket.SlaDeadline)
length += 32
}
length += 93
if input.Followup.SurveyLink != "" {
length += 19
length += len(input.Followup.SurveyLink)
length += 13
}
length += 2
if input.Followup.HasCompensation {
length += 89
length += len(input.Followup.CompensationType)
length += 13
length += len(var1)
length += 17
length += len(input.Followup.CompensationTarget)
length += 16
length += len(input.Followup.CompensationDate)
length += 1
}
length += 27
if input.Customer.Tier == "enterprise" {
length += 29
length += len(input.Customer.DedicatedSupportContact)
length += 19
length += len(input.Customer.EmergencyContact)
length += 1
}
length += 2
if input.Customer.Tier == "premium" {
length += 27
length += len(input.Support.PremiumPhone)
length += 24
length += len(var2)
length += 9
}
length += 23
length += len(input.Support.GeneralContact)
length += 34
length += len(input.Support.ChatUrl)
length += 21
length += len(input.Support.KbUrl)
length += 29
length += len(input.Ticket.Id)
length += 20
length += len(input.Agent.Name)
length += 6
length += len(input.Agent.Id)
length += 19
length += len(input.Session.EndTime)
length += 39
length += len(input.Customer.Name)
length += 8
b.Grow(length)
b.WriteString("## ๐ก Immediate Actions\n\n")
if input.Solution.HasQuickFix {
b.WriteString("\n### โ
Quick Resolution Available\n\n**Solution**: ")
b.WriteString(input.Solution.QuickFix)
b.WriteString("\n\n**Steps to Resolve**:\n1. ")
b.WriteString(input.Solution.Step1)
b.WriteString("\n2. ")
b.WriteString(input.Solution.Step2)
b.WriteString("\n3. ")
b.WriteString(input.Solution.Step3)
b.WriteString("\n\n")
if input.Solution.RequiresRestart {
b.WriteString("\nโ ๏ธ **System Restart Required**: Please restart ")
b.WriteString(input.Solution.RestartComponent)
b.WriteString(" after completing the above steps.\n")
}
b.WriteString("\n\n**Verification**: ")
b.WriteString(input.Solution.VerificationSteps)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Solution.RequiresInvestigation {
b.WriteString("\n### ๐ Further Investigation Needed\n\n**Investigation Timeline**: ")
b.WriteString(input.Investigation.Timeline)
b.WriteString("\n**Assigned Team**: ")
b.WriteString(input.Investigation.Team)
b.WriteString("\n**Progress Updates**: Every ")
b.WriteString(var0)
b.WriteString(" hours\n\n**What We're Checking**:\n- ")
b.WriteString(input.Investigation.Check1)
b.WriteString("\n- ")
b.WriteString(input.Investigation.Check2)
b.WriteString("\n- ")
b.WriteString(input.Investigation.Check3)
b.WriteString("\n\n**How We'll Contact You**: ")
b.WriteString(input.Investigation.ContactMethod)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ Ticket Status Update\n\n**Current Status**: ")
b.WriteString(input.Ticket.Status)
b.WriteString("\n**Priority**: ")
b.WriteString(input.Ticket.PriorityLevel)
b.WriteString("/5\n**Next Update**: ")
b.WriteString(input.Ticket.NextUpdate)
b.WriteString("\n\n")
if input.Ticket.SlaAtRisk {
b.WriteString("\nโ ๏ธ **SLA Alert**: This ticket is approaching SLA deadline (")
b.WriteString(input.Ticket.SlaDeadline)
b.WriteString(")\n**Escalation Triggered**: Yes\n")
}
b.WriteString("\n\n## ๐ฏ Customer Satisfaction\n\nBefore we close, how would you rate your experience today?\n\n")
if input.Followup.SurveyLink != "" {
b.WriteString("\n**Quick Survey**: ")
b.WriteString(input.Followup.SurveyLink)
b.WriteString(" (2 minutes)\n")
}
b.WriteString("\n\n")
if input.Followup.HasCompensation {
b.WriteString("\n### ๐ Service Recovery\n\nDue to the inconvenience, we're providing:\n**Compensation**: ")
b.WriteString(input.Followup.CompensationType)
b.WriteString("\n**Value**: $")
b.WriteString(var1)
b.WriteString("\n**Applied To**: ")
b.WriteString(input.Followup.CompensationTarget)
b.WriteString("\n**Effective**: ")
b.WriteString(input.Followup.CompensationDate)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ Need More Help?\n\n")
if input.Customer.Tier == "enterprise" {
b.WriteString("\n**Your Dedicated Support**: ")
b.WriteString(input.Customer.DedicatedSupportContact)
b.WriteString("\n**24/7 Hotline**: ")
b.WriteString(input.Customer.EmergencyContact)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Customer.Tier == "premium" {
b.WriteString("\n**Premium Support Line**: ")
b.WriteString(input.Support.PremiumPhone)
b.WriteString("\n**Average Wait Time**: ")
b.WriteString(var2)
b.WriteString(" minutes\n")
}
b.WriteString("\n\n**General Support**: ")
b.WriteString(input.Support.GeneralContact)
b.WriteString("\n**Live Chat**: Available 24/7 at ")
b.WriteString(input.Support.ChatUrl)
b.WriteString("\n**Knowledge Base**: ")
b.WriteString(input.Support.KbUrl)
b.WriteString("\n\n---\n**Ticket Reference**: #")
b.WriteString(input.Ticket.Id)
b.WriteString("\n**Support Agent**: ")
b.WriteString(input.Agent.Name)
b.WriteString(" (ID: ")
b.WriteString(input.Agent.Id)
b.WriteString(")\n**Session End**: ")
b.WriteString(input.Session.EndTime)
b.WriteString("\n\n*Thank you for choosing our service, ")
b.WriteString(input.Customer.Name)
b.WriteString("! ๐* ")
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package learningassistant
import (
"strconv"
"strings"
)
type SystemPrompt struct{}
func (input *SystemPrompt) String() string {
return "You are AdaptiveTutor, an AI learning assistant that personalizes education based on student progress, learning style, and current skill level. You provide engaging, interactive lessons with appropriate difficulty scaling."
}
type LessonIntroduction struct {
Subject string
Student struct {
Name string
Level string
LearningStyle string
CompletionPercentage int
StreakDays int
PreferredLanguage string
}
Lesson struct {
Topic string
Difficulty string
}
}
func (input *LessonIntroduction) isStudentZero() bool {
return input.Student.Name == "" &&
input.Student.Level == "" &&
input.Student.LearningStyle == "" &&
input.Student.CompletionPercentage == 0 &&
input.Student.StreakDays == 0 &&
input.Student.PreferredLanguage == ""
}
func (input *LessonIntroduction) isLessonZero() bool {
return input.Lesson.Topic == "" &&
input.Lesson.Difficulty == ""
}
func (input *LessonIntroduction) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Student.CompletionPercentage)
var1 := strconv.Itoa(input.Student.StreakDays)
length := 0
length += 7
length += len(input.Subject)
length += 22
length += len(input.Student.Name)
length += 92
length += len(input.Student.Level)
length += 23
length += len(input.Student.LearningStyle)
length += 17
length += len(var0)
length += 25
length += len(var1)
length += 12
if input.Student.PreferredLanguage != "" {
length += 31
length += len(input.Student.PreferredLanguage)
length += 2
}
length += 39
length += len(input.Lesson.Topic)
length += 17
length += len(input.Lesson.Difficulty)
length += 8
if input.Student.Level == "beginner" {
length += 100
}
length += 2
if input.Student.Level == "intermediate" {
length += 105
}
length += 2
if input.Student.Level == "advanced" {
length += 87
}
b.Grow(length)
b.WriteString("# ๐ ")
b.WriteString(input.Subject)
b.WriteString(" Learning Session\n\nHi ")
b.WriteString(input.Student.Name)
b.WriteString("! Welcome to your personalized learning session.\n\n## ๐ค Your Profile\n- **Current Level**: ")
b.WriteString(input.Student.Level)
b.WriteString("\n- **Learning Style**: ")
b.WriteString(input.Student.LearningStyle)
b.WriteString("\n- **Progress**: ")
b.WriteString(var0)
b.WriteString("% complete\n- **Streak**: ")
b.WriteString(var1)
b.WriteString(" days ๐ฅ\n\n")
if input.Student.PreferredLanguage != "" {
b.WriteString("\n*Lesson will be delivered in: ")
b.WriteString(input.Student.PreferredLanguage)
b.WriteString("*\n")
}
b.WriteString("\n\n## ๐ฏ Today's Objective\n**Topic**: ")
b.WriteString(input.Lesson.Topic)
b.WriteString("\n**Difficulty**: ")
b.WriteString(input.Lesson.Difficulty)
b.WriteString("/5 โญ\n\n")
if input.Student.Level == "beginner" {
b.WriteString("\nDon't worry if this seems challenging - we'll break everything down into simple, manageable steps!\n")
}
b.WriteString("\n\n")
if input.Student.Level == "intermediate" {
b.WriteString("\nYou're making great progress! Today we'll build on what you already know and explore some new concepts.\n")
}
b.WriteString("\n\n")
if input.Student.Level == "advanced" {
b.WriteString("\nReady for a challenge? Today's lesson will push your understanding to the next level.\n")
}
return b.String()
}
type AdaptiveContentDelivery struct {
Lesson struct {
HasPrerequisites bool
Prerequisites string
ReviewContent string
VisualContent string
HasDiagrams bool
Diagrams string
HasCharts bool
Charts string
AuditoryContent string
InteractiveExercises string
PracticeActivities string
ConceptCount int
}
Student struct {
PrerequisitesScore int
LearningStyle string
Level string
}
Concepts struct {
Concept1 string
Concept2 string
Concept3 string
Concept4 string
Concept5 string
}
Examples struct {
Basic string
BasicExplanation string
Intermediate string
IntermediateAnalysis string
IntermediateReasoning string
Advanced string
AdvancedAnalysis string
Optimizations string
}
}
func (input *AdaptiveContentDelivery) isLessonZero() bool {
return !input.Lesson.HasPrerequisites &&
input.Lesson.Prerequisites == "" &&
input.Lesson.ReviewContent == "" &&
input.Lesson.VisualContent == "" &&
!input.Lesson.HasDiagrams &&
input.Lesson.Diagrams == "" &&
!input.Lesson.HasCharts &&
input.Lesson.Charts == "" &&
input.Lesson.AuditoryContent == "" &&
input.Lesson.InteractiveExercises == "" &&
input.Lesson.PracticeActivities == "" &&
input.Lesson.ConceptCount == 0
}
func (input *AdaptiveContentDelivery) isStudentZero() bool {
return input.Student.PrerequisitesScore == 0 &&
input.Student.LearningStyle == "" &&
input.Student.Level == ""
}
func (input *AdaptiveContentDelivery) isConceptsZero() bool {
return input.Concepts.Concept1 == "" &&
input.Concepts.Concept2 == "" &&
input.Concepts.Concept3 == "" &&
input.Concepts.Concept4 == "" &&
input.Concepts.Concept5 == ""
}
func (input *AdaptiveContentDelivery) isExamplesZero() bool {
return input.Examples.Basic == "" &&
input.Examples.BasicExplanation == "" &&
input.Examples.Intermediate == "" &&
input.Examples.IntermediateAnalysis == "" &&
input.Examples.IntermediateReasoning == "" &&
input.Examples.Advanced == "" &&
input.Examples.AdvancedAnalysis == "" &&
input.Examples.Optimizations == ""
}
func (input *AdaptiveContentDelivery) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Student.PrerequisitesScore)
var1 := strconv.Itoa(input.Lesson.ConceptCount)
length := 0
if input.Lesson.HasPrerequisites {
length += 94
length += len(input.Lesson.Prerequisites)
length += 2
if input.Student.PrerequisitesScore != 0 {
length += 30
length += len(var0)
length += 6
if input.Student.PrerequisitesScore < 70 {
length += 97
length += len(input.Lesson.ReviewContent)
length += 1
}
length += 2
if input.Student.PrerequisitesScore >= 90 {
length += 56
}
length += 1
}
length += 1
}
length += 26
if input.Student.LearningStyle == "visual" {
length += 35
length += len(input.Lesson.VisualContent)
length += 2
if input.Lesson.HasDiagrams {
length += 27
length += len(input.Lesson.Diagrams)
length += 1
}
length += 2
if input.Lesson.HasCharts {
length += 22
length += len(input.Lesson.Charts)
length += 1
}
length += 1
}
length += 2
if input.Student.LearningStyle == "auditory" {
length += 37
length += len(input.Lesson.AuditoryContent)
length += 91
}
length += 2
if input.Student.LearningStyle == "kinesthetic" {
length += 100
length += len(input.Lesson.InteractiveExercises)
length += 27
length += len(input.Lesson.PracticeActivities)
length += 1
}
length += 25
if input.Lesson.ConceptCount != 0 {
length += 13
length += len(var1)
length += 26
length += len(input.Concepts.Concept1)
length += 4
length += len(input.Concepts.Concept2)
length += 1
if input.Lesson.ConceptCount >= 3 {
length += 4
length += len(input.Concepts.Concept3)
length += 1
}
length += 1
if input.Lesson.ConceptCount >= 4 {
length += 4
length += len(input.Concepts.Concept4)
length += 1
}
length += 1
if input.Lesson.ConceptCount >= 5 {
length += 4
length += len(input.Concepts.Concept5)
length += 1
}
length += 1
}
length += 20
if input.Student.Level == "beginner" {
length += 59
length += len(input.Examples.Basic)
length += 23
length += len(input.Examples.BasicExplanation)
length += 1
}
length += 2
if input.Student.Level == "intermediate" {
length += 58
length += len(input.Examples.Intermediate)
length += 20
length += len(input.Examples.IntermediateAnalysis)
length += 22
length += len(input.Examples.IntermediateReasoning)
length += 1
}
length += 2
if input.Student.Level == "advanced" {
length += 68
length += len(input.Examples.Advanced)
length += 21
length += len(input.Examples.AdvancedAnalysis)
length += 34
length += len(input.Examples.Optimizations)
length += 1
}
b.Grow(length)
if input.Lesson.HasPrerequisites {
b.WriteString("\n## ๐ Prerequisites Check\nBefore we start, let's make sure you understand these concepts:\n\n")
b.WriteString(input.Lesson.Prerequisites)
b.WriteString("\n\n")
if input.Student.PrerequisitesScore != 0 {
b.WriteString("\n**Your prerequisite score**: ")
b.WriteString(var0)
b.WriteString("/100\n\n")
if input.Student.PrerequisitesScore < 70 {
b.WriteString("\n๐ **Recommendation**: Let's do a quick review of the basics first.\n\n### Quick Review Session\n")
b.WriteString(input.Lesson.ReviewContent)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Student.PrerequisitesScore >= 90 {
b.WriteString("\nโ
**Excellent!** You're ready for advanced concepts.\n")
}
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ Lesson Content\n\n")
if input.Student.LearningStyle == "visual" {
b.WriteString("\n### ๐ Visual Learning Approach\n")
b.WriteString(input.Lesson.VisualContent)
b.WriteString("\n\n")
if input.Lesson.HasDiagrams {
b.WriteString("\n**Interactive Diagrams:**\n")
b.WriteString(input.Lesson.Diagrams)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Lesson.HasCharts {
b.WriteString("\n**Charts & Graphs:**\n")
b.WriteString(input.Lesson.Charts)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Student.LearningStyle == "auditory" {
b.WriteString("\n### ๐ต Auditory Learning Approach\n")
b.WriteString(input.Lesson.AuditoryContent)
b.WriteString("\n\n*๐ก Tip: Try reading this lesson out loud or use text-to-speech for better retention!*\n")
}
b.WriteString("\n\n")
if input.Student.LearningStyle == "kinesthetic" {
b.WriteString("\n### ๐ ๏ธ Hands-On Learning Approach\nLet's learn by doing! Here are some interactive exercises:\n\n")
b.WriteString(input.Lesson.InteractiveExercises)
b.WriteString("\n\n**Practice Activities:**\n")
b.WriteString(input.Lesson.PracticeActivities)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ง Core Concepts\n\n")
if input.Lesson.ConceptCount != 0 {
b.WriteString("\nWe'll cover ")
b.WriteString(var1)
b.WriteString(" main concepts today:\n\n1. ")
b.WriteString(input.Concepts.Concept1)
b.WriteString("\n2. ")
b.WriteString(input.Concepts.Concept2)
b.WriteRune('\n')
if input.Lesson.ConceptCount >= 3 {
b.WriteString("\n3. ")
b.WriteString(input.Concepts.Concept3)
b.WriteRune('\n')
}
b.WriteRune('\n')
if input.Lesson.ConceptCount >= 4 {
b.WriteString("\n4. ")
b.WriteString(input.Concepts.Concept4)
b.WriteRune('\n')
}
b.WriteRune('\n')
if input.Lesson.ConceptCount >= 5 {
b.WriteString("\n5. ")
b.WriteString(input.Concepts.Concept5)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ก Examples\n\n")
if input.Student.Level == "beginner" {
b.WriteString("\n### Simple Example\nLet's start with a basic example:\n\n```\n")
b.WriteString(input.Examples.Basic)
b.WriteString("\n```\n\n**Explanation**: ")
b.WriteString(input.Examples.BasicExplanation)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Student.Level == "intermediate" {
b.WriteString("\n### Practical Example\nHere's a real-world scenario:\n\n```\n")
b.WriteString(input.Examples.Intermediate)
b.WriteString("\n```\n\n**Analysis**: ")
b.WriteString(input.Examples.IntermediateAnalysis)
b.WriteString("\n\n**Why this works**: ")
b.WriteString(input.Examples.IntermediateReasoning)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Student.Level == "advanced" {
b.WriteString("\n### Complex Example\nLet's examine an advanced implementation:\n\n```\n")
b.WriteString(input.Examples.Advanced)
b.WriteString("\n```\n\n**Deep Dive**: ")
b.WriteString(input.Examples.AdvancedAnalysis)
b.WriteString("\n\n**Optimization Opportunities**: ")
b.WriteString(input.Examples.Optimizations)
b.WriteRune('\n')
}
return b.String()
}
type InteractiveAssessment struct {
Assessment struct {
HasQuiz bool
HasProject bool
}
Question struct {
Number int
Text string
Type string
OptionA string
OptionB string
OptionC string
OptionD string
CodeTemplate string
ExpectedOutput string
Prompt string
Hint string
}
Project struct {
Title string
EstimatedMinutes int
Requirements string
Language string
StarterCode string
HasBonus bool
BonusChallenge string
}
}
func (input *InteractiveAssessment) isAssessmentZero() bool {
return !input.Assessment.HasQuiz &&
!input.Assessment.HasProject
}
func (input *InteractiveAssessment) isQuestionZero() bool {
return input.Question.Number == 0 &&
input.Question.Text == "" &&
input.Question.Type == "" &&
input.Question.OptionA == "" &&
input.Question.OptionB == "" &&
input.Question.OptionC == "" &&
input.Question.OptionD == "" &&
input.Question.CodeTemplate == "" &&
input.Question.ExpectedOutput == "" &&
input.Question.Prompt == "" &&
input.Question.Hint == ""
}
func (input *InteractiveAssessment) isProjectZero() bool {
return input.Project.Title == "" &&
input.Project.EstimatedMinutes == 0 &&
input.Project.Requirements == "" &&
input.Project.Language == "" &&
input.Project.StarterCode == "" &&
!input.Project.HasBonus &&
input.Project.BonusChallenge == ""
}
func (input *InteractiveAssessment) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Question.Number)
var1 := strconv.Itoa(input.Project.EstimatedMinutes)
length := 0
length += 24
if input.Assessment.HasQuiz {
length += 39
length += len(var0)
length += 4
length += len(input.Question.Text)
length += 2
if input.Question.Type == "multiple_choice" {
length += 4
length += len(input.Question.OptionA)
length += 4
length += len(input.Question.OptionB)
length += 4
length += len(input.Question.OptionC)
length += 4
length += len(input.Question.OptionD)
length += 1
}
length += 2
if input.Question.Type == "coding" {
length += 27
length += len(input.Question.CodeTemplate)
length += 27
length += len(input.Question.ExpectedOutput)
length += 1
}
length += 2
if input.Question.Type == "explanation" {
length += 32
length += len(input.Question.Prompt)
length += 22
length += len(input.Question.Hint)
length += 1
}
length += 1
}
length += 2
if input.Assessment.HasProject {
length += 36
length += len(input.Project.Title)
length += 21
length += len(var1)
length += 28
length += len(input.Project.Requirements)
length += 23
length += len(input.Project.Language)
length += 1
length += len(input.Project.StarterCode)
length += 6
if input.Project.HasBonus {
length += 22
length += len(input.Project.BonusChallenge)
length += 1
}
length += 1
}
b.Grow(length)
b.WriteString("## ๐ฏ Practice Time!\n\n")
if input.Assessment.HasQuiz {
b.WriteString("\n### Quick Knowledge Check\n\n**Question ")
b.WriteString(var0)
b.WriteString("**: ")
b.WriteString(input.Question.Text)
b.WriteString("\n\n")
if input.Question.Type == "multiple_choice" {
b.WriteString("\nA) ")
b.WriteString(input.Question.OptionA)
b.WriteString("\nB) ")
b.WriteString(input.Question.OptionB)
b.WriteString("\nC) ")
b.WriteString(input.Question.OptionC)
b.WriteString("\nD) ")
b.WriteString(input.Question.OptionD)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Question.Type == "coding" {
b.WriteString("\n**Coding Challenge:**\n```\n")
b.WriteString(input.Question.CodeTemplate)
b.WriteString("\n```\n\n**Expected Output**: ")
b.WriteString(input.Question.ExpectedOutput)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Question.Type == "explanation" {
b.WriteString("\n**Explain in your own words**: ")
b.WriteString(input.Question.Prompt)
b.WriteString("\n\n*Hint*: Think about ")
b.WriteString(input.Question.Hint)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Assessment.HasProject {
b.WriteString("\n## ๐ Mini Project\n\n**Project**: ")
b.WriteString(input.Project.Title)
b.WriteString("\n**Estimated Time**: ")
b.WriteString(var1)
b.WriteString(" minutes\n\n**Requirements**:\n")
b.WriteString(input.Project.Requirements)
b.WriteString("\n\n**Starter Code**:\n```")
b.WriteString(input.Project.Language)
b.WriteRune('\n')
b.WriteString(input.Project.StarterCode)
b.WriteString("\n```\n\n")
if input.Project.HasBonus {
b.WriteString("\n**Bonus Challenge**: ")
b.WriteString(input.Project.BonusChallenge)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
return b.String()
}
type ProgressTracking struct {
Progress struct {
CurrentLesson int
TotalLessons int
StrugglingAreas string
StrengthAreas string
}
Student struct {
CompletionPercentage int
StreakDays int
Name string
}
NextSteps struct {
FinalAssessment string
NextTopic string
StudyMinutes int
HasHomework bool
Homework string
}
Session struct {
Timestamp string
}
}
func (input *ProgressTracking) isProgressZero() bool {
return input.Progress.CurrentLesson == 0 &&
input.Progress.TotalLessons == 0 &&
input.Progress.StrugglingAreas == "" &&
input.Progress.StrengthAreas == ""
}
func (input *ProgressTracking) isStudentZero() bool {
return input.Student.CompletionPercentage == 0 &&
input.Student.StreakDays == 0 &&
input.Student.Name == ""
}
func (input *ProgressTracking) isNextStepsZero() bool {
return input.NextSteps.FinalAssessment == "" &&
input.NextSteps.NextTopic == "" &&
input.NextSteps.StudyMinutes == 0 &&
!input.NextSteps.HasHomework &&
input.NextSteps.Homework == ""
}
func (input *ProgressTracking) isSessionZero() bool {
return input.Session.Timestamp == ""
}
func (input *ProgressTracking) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Progress.CurrentLesson)
var1 := strconv.Itoa(input.Progress.TotalLessons)
var2 := strconv.Itoa(input.Student.StreakDays)
var3 := strconv.Itoa(input.NextSteps.StudyMinutes)
length := 0
length += 46
length += len(var0)
length += 1
length += len(var1)
length += 31
length += len(strconv.Itoa(input.Student.CompletionPercentage))
length += 3
if input.Student.StreakDays >= 7 {
length += 31
length += len(var2)
length += 23
if input.Student.StreakDays >= 30 {
length += 66
}
length += 2
if input.Progress.StrugglingAreas != "" {
length += 74
length += len(input.Progress.StrugglingAreas)
length += 1
}
length += 2
if input.Progress.StrengthAreas != "" {
length += 46
length += len(input.Progress.StrengthAreas)
length += 1
}
length += 22
if input.Student.CompletionPercentage >= 80 {
length += 74
length += len(input.NextSteps.FinalAssessment)
length += 1
}
length += 2
if input.Student.CompletionPercentage < 80 {
length += 18
length += len(input.NextSteps.NextTopic)
length += 29
length += len(var3)
length += 10
if input.NextSteps.HasHomework {
length += 26
length += len(input.NextSteps.Homework)
length += 1
}
length += 1
}
length += 31
length += len(input.Student.Name)
length += 31
length += len(input.Session.Timestamp)
length += 2
}
b.Grow(length)
b.WriteString("## ๐ Your Progress\n\n**Lesson Completion**: ")
b.WriteString(var0)
b.WriteString("/")
b.WriteString(var1)
b.WriteString(" lessons\n**Overall Progress**: ")
b.WriteString(strconv.Itoa(input.Student.CompletionPercentage))
b.WriteString("%\n\n")
if input.Student.StreakDays >= 7 {
b.WriteString("\n๐ **Amazing!** You're on a ")
b.WriteString(var2)
b.WriteString("-day learning streak!\n\n")
if input.Student.StreakDays >= 30 {
b.WriteString("\n๐ **Legendary Learner!** 30+ day streak - you're unstoppable!\n")
}
b.WriteString("\n\n")
if input.Progress.StrugglingAreas != "" {
b.WriteString("\n### ๐ Areas for Review\nBased on your performance, consider reviewing:\n")
b.WriteString(input.Progress.StrugglingAreas)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Progress.StrengthAreas != "" {
b.WriteString("\n### ๐ช Your Strengths\nYou're excelling in:\n")
b.WriteString(input.Progress.StrengthAreas)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ฏ Next Steps\n\n")
if input.Student.CompletionPercentage >= 80 {
b.WriteString("\n๐ **Almost there!** You're ready for the final assessment.\n\n**Next**: ")
b.WriteString(input.NextSteps.FinalAssessment)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Student.CompletionPercentage < 80 {
b.WriteString("\n**Next Lesson**: ")
b.WriteString(input.NextSteps.NextTopic)
b.WriteString("\n**Recommended Study Time**: ")
b.WriteString(var3)
b.WriteString(" minutes\n\n")
if input.NextSteps.HasHomework {
b.WriteString("\n**Homework Assignment**: ")
b.WriteString(input.NextSteps.Homework)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n---\n*Keep up the great work, ")
b.WriteString(input.Student.Name)
b.WriteString("! ๐*\n*Session completed at: ")
b.WriteString(input.Session.Timestamp)
b.WriteString("* ")
}
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package minimaltest
import (
"strconv"
"strings"
)
type TestBasic struct {
Name string
}
func (input *TestBasic) String() string {
var b strings.Builder
length := 0
length += 29
length += len(input.Name)
length += 1
b.Grow(length)
b.WriteString("Simple test with backticks: `")
b.WriteString(input.Name)
b.WriteString("`")
return b.String()
}
type TestConditional struct {
User struct {
Premium bool
}
}
func (input *TestConditional) isUserZero() bool {
return !input.User.Premium
}
func (input *TestConditional) String() string {
var b strings.Builder
length := 0
if input.User.Premium {
length += 31
} else {
length += 31
}
b.Grow(length)
if input.User.Premium {
b.WriteString("\nPremium user with `backticks`\n")
} else {
b.WriteString("\nRegular user with `backticks`\n")
}
return b.String()
}
type TestNested struct {
Outer string
Inner string
}
func (input *TestNested) String() string {
var b strings.Builder
length := 0
if input.Outer != "" {
length += 13
if input.Inner != "" {
length += 15
}
length += 11
}
b.Grow(length)
if input.Outer != "" {
b.WriteString("\nOuter start\n")
if input.Inner != "" {
b.WriteString("\nInner content\n")
}
b.WriteString("\nOuter end\n")
}
return b.String()
}
type TestComparison struct {
Age int
}
func (input *TestComparison) String() string {
var b strings.Builder
var0 := strconv.Itoa(input.Age)
length := 0
if input.Age >= 18 {
length += 24
length += len(var0)
length += 2
} else {
length += 23
length += len(var0)
length += 2
}
length += 1
b.Grow(length)
if input.Age >= 18 {
b.WriteString("\nYou are an adult (age: ")
b.WriteString(var0)
b.WriteString(")\n")
} else {
b.WriteString("\nYou are a minor (age: ")
b.WriteString(var0)
b.WriteString(")\n")
}
b.WriteString(" ")
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package simpleapidocs
import (
"strconv"
"strings"
)
type ApiDocumentationGenerator struct{}
func (input *ApiDocumentationGenerator) String() string {
return "You are APIDocBot, an intelligent documentation generator that creates comprehensive, up-to-date API documentation with interactive examples, versioning support, and developer-friendly formatting."
}
type ApiOverview struct {
Api struct {
Name string
Version string
BaseUrl string
Protocol string
AuthType string
Deprecated bool
ReplacementVersion string
SunsetDate string
Beta bool
StabilityLevel string
}
Changelog struct {
HasBreakingChanges bool
BreakingChanges string
NewFeatures string
Improvements string
BugFixes string
}
Docs struct {
MigrationUrl string
}
Auth struct {
Type string
Required bool
DashboardUrl string
OauthFlow string
Scopes string
AuthUrl string
TokenUrl string
HasScopes bool
ScopeList string
HeaderName string
QueryParam string
RateLimits bool
}
RateLimits struct {
StandardRequests int
StandardWindow string
PremiumRequests int
PremiumWindow string
HasBurst bool
BurstRequests int
BurstWindow string
}
}
func (input *ApiOverview) isApiZero() bool {
return input.Api.Name == "" &&
input.Api.Version == "" &&
input.Api.BaseUrl == "" &&
input.Api.Protocol == "" &&
input.Api.AuthType == "" &&
!input.Api.Deprecated &&
input.Api.ReplacementVersion == "" &&
input.Api.SunsetDate == "" &&
!input.Api.Beta &&
input.Api.StabilityLevel == ""
}
func (input *ApiOverview) isChangelogZero() bool {
return !input.Changelog.HasBreakingChanges &&
input.Changelog.BreakingChanges == "" &&
input.Changelog.NewFeatures == "" &&
input.Changelog.Improvements == "" &&
input.Changelog.BugFixes == ""
}
func (input *ApiOverview) isDocsZero() bool {
return input.Docs.MigrationUrl == ""
}
func (input *ApiOverview) isAuthZero() bool {
return input.Auth.Type == "" &&
!input.Auth.Required &&
input.Auth.DashboardUrl == "" &&
input.Auth.OauthFlow == "" &&
input.Auth.Scopes == "" &&
input.Auth.AuthUrl == "" &&
input.Auth.TokenUrl == "" &&
!input.Auth.HasScopes &&
input.Auth.ScopeList == "" &&
input.Auth.HeaderName == "" &&
input.Auth.QueryParam == "" &&
!input.Auth.RateLimits
}
func (input *ApiOverview) isRateLimitsZero() bool {
return input.RateLimits.StandardRequests == 0 &&
input.RateLimits.StandardWindow == "" &&
input.RateLimits.PremiumRequests == 0 &&
input.RateLimits.PremiumWindow == "" &&
!input.RateLimits.HasBurst &&
input.RateLimits.BurstRequests == 0 &&
input.RateLimits.BurstWindow == ""
}
func (input *ApiOverview) String() string {
var b strings.Builder
var0 := strconv.FormatBool(input.Auth.Required)
var1 := strconv.FormatBool(input.Auth.HasScopes)
var2 := strconv.Itoa(input.RateLimits.StandardRequests)
var3 := strconv.Itoa(input.RateLimits.PremiumRequests)
var4 := strconv.Itoa(input.RateLimits.BurstRequests)
length := 0
length += 7
length += len(input.Api.Name)
length += 33
length += len(input.Api.Version)
length += 16
length += len(input.Api.BaseUrl)
length += 16
length += len(input.Api.Protocol)
length += 21
length += len(input.Api.AuthType)
length += 2
if input.Api.Deprecated {
length += 86
length += len(input.Api.ReplacementVersion)
length += 19
length += len(input.Api.SunsetDate)
length += 1
}
length += 2
if input.Api.Beta {
length += 92
length += len(input.Api.StabilityLevel)
length += 3
}
length += 25
length += len(input.Api.Version)
length += 2
if input.Changelog.HasBreakingChanges {
length += 27
length += len(input.Changelog.BreakingChanges)
length += 59
length += len(input.Docs.MigrationUrl)
length += 28
}
length += 2
if input.Changelog.NewFeatures != "" {
length += 22
length += len(input.Changelog.NewFeatures)
length += 1
}
length += 2
if input.Changelog.Improvements != "" {
length += 23
length += len(input.Changelog.Improvements)
length += 1
}
length += 2
if input.Changelog.BugFixes != "" {
length += 20
length += len(input.Changelog.BugFixes)
length += 1
}
length += 36
length += len(input.Auth.Type)
length += 15
length += len(var0)
length += 2
if input.Auth.Type == "bearer" {
length += 176
length += len(input.Auth.DashboardUrl)
length += 2
}
length += 2
if input.Auth.Type == "oauth2" {
length += 40
length += len(input.Auth.OauthFlow)
length += 13
length += len(input.Auth.Scopes)
length += 26
length += len(input.Auth.AuthUrl)
length += 18
length += len(input.Auth.TokenUrl)
length += 3
if input.Auth.HasScopes {
length += 23
length += len(input.Auth.ScopeList)
length += 1
} else {
length += 13
length += len(var1)
length += 1
}
length += 1
}
length += 2
if input.Auth.Type == "apikey" {
length += 41
length += len(input.Auth.HeaderName)
length += 24
length += len(input.Auth.QueryParam)
length += 25
length += len(input.Auth.HeaderName)
length += 19
}
length += 2
if input.Auth.RateLimits {
length += 41
length += len(var2)
length += 10
length += len(input.RateLimits.StandardWindow)
length += 19
length += len(var3)
length += 10
length += len(input.RateLimits.PremiumWindow)
length += 165
if input.RateLimits.HasBurst {
length += 22
length += len(var4)
length += 13
length += len(input.RateLimits.BurstWindow)
length += 9
}
length += 1
}
length += 1
b.Grow(length)
b.WriteString("# ๐ก ")
b.WriteString(input.Api.Name)
b.WriteString(" API Documentation\n\n**Version**: ")
b.WriteString(input.Api.Version)
b.WriteString("\n**Base URL**: `")
b.WriteString(input.Api.BaseUrl)
b.WriteString("`\n**Protocol**: ")
b.WriteString(input.Api.Protocol)
b.WriteString("\n**Authentication**: ")
b.WriteString(input.Api.AuthType)
b.WriteString("\n\n")
if input.Api.Deprecated {
b.WriteString("\nโ ๏ธ **DEPRECATED API**: This API version is deprecated. Please migrate to version ")
b.WriteString(input.Api.ReplacementVersion)
b.WriteString(".\n**Sunset Date**: ")
b.WriteString(input.Api.SunsetDate)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Api.Beta {
b.WriteString("\n๐งช **BETA API**: This API is in beta. Features may change without notice.\n**Stability**: ")
b.WriteString(input.Api.StabilityLevel)
b.WriteString("/5\n")
}
b.WriteString("\n\n## ๐ What's New in v")
b.WriteString(input.Api.Version)
b.WriteString("\n\n")
if input.Changelog.HasBreakingChanges {
b.WriteString("\n### ๐ฅ Breaking Changes\n")
b.WriteString(input.Changelog.BreakingChanges)
b.WriteString("\n\nโ ๏ธ **Migration Required**: See our [migration guide](")
b.WriteString(input.Docs.MigrationUrl)
b.WriteString(") for upgrade instructions.\n")
}
b.WriteString("\n\n")
if input.Changelog.NewFeatures != "" {
b.WriteString("\n### โจ New Features\n")
b.WriteString(input.Changelog.NewFeatures)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Changelog.Improvements != "" {
b.WriteString("\n### ๐ Improvements\n")
b.WriteString(input.Changelog.Improvements)
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Changelog.BugFixes != "" {
b.WriteString("\n### ๐ Bug Fixes\n")
b.WriteString(input.Changelog.BugFixes)
b.WriteRune('\n')
}
b.WriteString("\n\n## ๐ Authentication\n\n**Type**: ")
b.WriteString(input.Auth.Type)
b.WriteString("\n**Required**: ")
b.WriteString(var0)
b.WriteString("\n\n")
if input.Auth.Type == "bearer" {
b.WriteString("\n### Bearer Token Authentication\nInclude your API key in the Authorization header:\n\n```http\nAuthorization: Bearer YOUR_API_KEY\n```\n\n**Get your API key**: [Developer Dashboard](")
b.WriteString(input.Auth.DashboardUrl)
b.WriteString(")\n")
}
b.WriteString("\n\n")
if input.Auth.Type == "oauth2" {
b.WriteString("\n### OAuth 2.0 Authentication\n**Flow**: ")
b.WriteString(input.Auth.OauthFlow)
b.WriteString("\n**Scopes**: ")
b.WriteString(input.Auth.Scopes)
b.WriteString("\n\n**Authorization URL**: `")
b.WriteString(input.Auth.AuthUrl)
b.WriteString("`\n**Token URL**: `")
b.WriteString(input.Auth.TokenUrl)
b.WriteString("`\n\n")
if input.Auth.HasScopes {
b.WriteString("\n#### Required Scopes:\n")
b.WriteString(input.Auth.ScopeList)
b.WriteRune('\n')
} else {
b.WriteString("\nHas Scopes: ")
b.WriteString(var1)
b.WriteRune('\n')
}
b.WriteRune('\n')
}
b.WriteString("\n\n")
if input.Auth.Type == "apikey" {
b.WriteString("\n### API Key Authentication\n**Header**: `")
b.WriteString(input.Auth.HeaderName)
b.WriteString("`\n**Query Parameter**: `")
b.WriteString(input.Auth.QueryParam)
b.WriteString("` (alternative)\n\n```http\n")
b.WriteString(input.Auth.HeaderName)
b.WriteString(": YOUR_API_KEY\n```\n")
}
b.WriteString("\n\n")
if input.Auth.RateLimits {
b.WriteString("\n## ๐ Rate Limits\n\n**Standard Tier**: ")
b.WriteString(var2)
b.WriteString(" requests/")
b.WriteString(input.RateLimits.StandardWindow)
b.WriteString("\n**Premium Tier**: ")
b.WriteString(var3)
b.WriteString(" requests/")
b.WriteString(input.RateLimits.PremiumWindow)
b.WriteString("\n\n**Rate Limit Headers**:\n- `X-RateLimit-Limit`: Total requests allowed\n- `X-RateLimit-Remaining`: Requests remaining\n- `X-RateLimit-Reset`: Time when limit resets\n\n")
if input.RateLimits.HasBurst {
b.WriteString("\n**Burst Allowance**: ")
b.WriteString(var4)
b.WriteString(" requests in ")
b.WriteString(input.RateLimits.BurstWindow)
b.WriteString(" seconds\n")
}
b.WriteRune('\n')
}
b.WriteRune('\n')
return b.String()
}
// Code generated by agentflow v0.5.1; DO NOT EDIT.
package simple
import (
"strings"
)
type SystemPrompt struct{}
func (input *SystemPrompt) String() string {
return "You are HelloBot, a simple assistant that loves to say hello to users.\nYour only purpose is to greet users in a friendly way and say \"Hello, World!\" with some variation.\nKeep your responses short, cheerful, and hello-focused."
}
type CreateTitle struct {
Messages string
}
func (input *CreateTitle) String() string {
var b strings.Builder
length := 0
length += 54
length += len(input.Messages)
length += 7
b.Grow(length)
b.WriteString("Create a simple hello-themed title for this exchange:\n")
b.WriteString(input.Messages)
b.WriteString("\ntitle:")
return b.String()
}
type ChatWithUser struct {
PreviousMessages string
}
func (input *ChatWithUser) String() string {
var b strings.Builder
length := 0
length += 41
length += len(input.PreviousMessages)
length += 10
b.Grow(length)
b.WriteString("Say hello to the user in a cheerful way:\n")
b.WriteString(input.PreviousMessages)
b.WriteString("\nHelloBot:")
return b.String()
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package assert
import (
"fmt"
"log/slog"
"os"
"runtime"
)
func NoError(err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
slog.Error(fmt.Sprintf(
"%s:%d: unexpected error: %v",
file, line, err,
))
os.Exit(1)
}
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package require
import (
"errors"
"fmt"
"runtime"
"strings"
"testing"
)
type Equaler[T any] interface {
Equal(actual T) bool
}
func EqualErr(t *testing.T, want, got error) {
if !errors.Is(want, got) {
// Get caller info for clickable file:line
_, file, line, ok := runtime.Caller(1)
if ok {
t.Fatalf("%s:%d: expected %v, got %v", file, line, want, got)
} else {
t.Fatalf("expected %v, got %v", want, got)
}
}
}
func Equal[T Equaler[T]](t *testing.T, want, got T) {
if !want.Equal(got) {
var sb strings.Builder
// Get caller info for clickable file:line
_, file, line, ok := runtime.Caller(1)
if ok {
sb.WriteString(fmt.Sprintf("%s:%d: ", file, line))
}
sb.WriteString("Should equal:\n")
WantGotDiff(&sb, want, got)
t.Fatal(sb.String())
}
}
func NotEqual[T Equaler[T]](t *testing.T, want, got T) {
if want.Equal(got) {
var sb strings.Builder
// Get caller info for clickable file:line
_, file, line, ok := runtime.Caller(1)
if ok {
sb.WriteString(fmt.Sprintf("%s:%d: ", file, line))
}
sb.WriteString("Should not equal:\n")
WantGotDiff(&sb, want, got)
t.Fatal(sb.String())
}
}
func NoError(t *testing.T, err error) {
if err != nil {
// Get caller info for clickable file:line
_, file, line, ok := runtime.Caller(1)
if ok {
t.Fatalf("%s:%d: expected no error, got %v", file, line, err)
} else {
t.Fatalf("expected no error, got %v", err)
}
}
}
// makeWhitespaceVisible replaces whitespace characters with visible representations
func makeWhitespaceVisible(s string) string {
s = strings.ReplaceAll(s, " ", "ยท")
s = strings.ReplaceAll(s, "\t", "โ")
s = strings.ReplaceAll(s, "\n", "โต\n")
s = strings.ReplaceAll(s, "\r", "โคด")
return s
}
// WantGotDiff shows a diff-style comparison with visible whitespace
func WantGotDiff(sb *strings.Builder, want, got any) {
var wantStr, gotStr string
if s, ok := want.(fmt.Stringer); ok {
wantStr = s.String()
} else {
wantStr = fmt.Sprintf("%+v", want)
}
if s, ok := got.(fmt.Stringer); ok {
gotStr = s.String()
} else {
gotStr = fmt.Sprintf("%+v", got)
}
wantLines := strings.Split(wantStr, "\n")
gotLines := strings.Split(gotStr, "\n")
sb.WriteString("\n\x1b[1mDIFF:\x1b[0m\n")
maxLines := len(wantLines)
if len(gotLines) > maxLines {
maxLines = len(gotLines)
}
for i := 0; i < maxLines; i++ {
var wantLine, gotLine string
if i < len(wantLines) {
wantLine = wantLines[i]
}
if i < len(gotLines) {
gotLine = gotLines[i]
}
if wantLine == gotLine {
sb.WriteString(fmt.Sprintf(" %s\n", makeWhitespaceVisible(wantLine)))
} else {
if wantLine != "" {
sb.WriteString(fmt.Sprintf("\x1b[31m- %s\x1b[0m\n", makeWhitespaceVisible(wantLine)))
}
if gotLine != "" {
sb.WriteString(fmt.Sprintf("\x1b[32m+ %s\x1b[0m\n", makeWhitespaceVisible(gotLine)))
}
}
}
}
func WantGot(sb *strings.Builder, want, got any) {
sb.WriteString("\x1b[1mWANT:\x1b[0m\n")
if s, ok := want.(fmt.Stringer); ok {
sb.WriteString(s.String())
} else {
fmt.Fprintf(sb, "%+v", want)
}
sb.WriteString("\n\x1b[1mGOT:\x1b[0m\n")
if s, ok := got.(fmt.Stringer); ok {
sb.WriteString(s.String())
} else {
fmt.Fprintf(sb, "%+v", got)
}
}
func WantGotBoldQuotes(sb *strings.Builder, want, got any) {
sb.WriteString("\x1b[1mWANT:\x1b[0m\n")
sb.WriteString("\x1b[1m|\x1b[0m")
if s, ok := want.(fmt.Stringer); ok {
sb.WriteString(s.String())
} else {
fmt.Fprintf(sb, "%+v", want)
}
sb.WriteString("\x1b[1m|\x1b[0m")
sb.WriteString("\n\x1b[1mGOT:\x1b[0m\n")
sb.WriteString("\x1b[1m|\x1b[0m")
if s, ok := got.(fmt.Stringer); ok {
sb.WriteString(s.String())
} else {
fmt.Fprintf(sb, "%+v", got)
}
sb.WriteString("\x1b[1m|\x1b[0m")
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ast
import (
"bytes"
"fmt"
"log/slog"
"slices"
"strings"
"github.com/omniaura/agentflow/pkg/token"
"github.com/omniaura/agentflow/pkg/token/coarse"
"github.com/peyton-spencer/caseconv"
"github.com/peyton-spencer/caseconv/bytcase"
)
type File struct {
Name string
Content []byte
Prompts []Prompt
}
func (f File) String() string {
var sb strings.Builder
sb.WriteString("File{\n")
sb.WriteString(fmt.Sprintf(" Name: %s,\n", f.Name))
sb.WriteString(" Prompts: [\n")
for i, prompt := range f.Prompts {
sb.WriteString(" ")
sb.WriteString(prompt.Stringify(f.Content))
if i < len(f.Prompts)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
sb.WriteString(" ]\n")
sb.WriteRune('}')
return sb.String()
}
func (f1 File) Equal(f2 File) bool {
e := f1.Name == f2.Name && bytes.Equal(f1.Content, f2.Content)
if !e {
return false
}
if len(f1.Prompts) != len(f2.Prompts) {
return false
}
for i, p1 := range f1.Prompts {
if !p1.Equal(f2.Prompts[i]) {
return false
}
}
return true
}
type Prompt struct {
// Title is the name of the prompt.
Title coarse.Token
// Nodes are the nodes of the prompt.
Nodes []coarse.Token
}
func (p Prompt) Stringify(content []byte) string {
var buf strings.Builder
buf.WriteString("Prompt{Title: ")
buf.Write(content[p.Title.Start:p.Title.End])
buf.WriteString(", Nodes: ")
for i, node := range p.Nodes {
buf.WriteString(fmt.Sprintf("%v: [%d:%d] %q", node.Kind, node.Start, node.End, node.Get(content)))
if i < len(p.Nodes)-1 {
buf.WriteString(", ")
}
}
buf.WriteRune('}')
return buf.String()
}
func (p Prompt) Vars(content []byte, c caseconv.Case) (vars [][]byte, length int) {
vars = make([][]byte, 0, len(p.Nodes))
for _, node := range p.Nodes {
if node.Kind == coarse.Var {
name := node.Get(content)
if slices.ContainsFunc(vars, func(b []byte) bool { return bytes.Equal(b, name) }) {
continue
}
vars = append(vars, name)
}
}
for i := range vars {
switch c {
case caseconv.CaseCamel:
vars[i] = bytcase.ToLowerCamel(vars[i])
case caseconv.CaseSnake:
vars[i] = bytcase.ToSnake(vars[i])
}
length += len(vars[i])
}
return
}
type InputStruct struct {
TopLevel []InputNode
}
func (i InputStruct) Equal(other InputStruct) bool {
return slices.EqualFunc(i.TopLevel, other.TopLevel, InputNode.Equal)
}
func (i InputStruct) String() string {
var buf strings.Builder
buf.WriteString("type Input struct {\n")
for _, n := range i.TopLevel {
buf.WriteString(" ")
buf.Write(n.Name)
buf.WriteString(" ")
buf.WriteString(n.String())
buf.WriteString("\n")
}
buf.WriteString("}")
return buf.String()
}
type InputNode struct {
Name []byte
Type string // New: type of the variable ("string", "int", "bool", etc.)
Subnodes []InputNode
}
func (n InputNode) Equal(other InputNode) bool {
return bytes.Equal(n.Name, other.Name) && n.Type == other.Type && slices.EqualFunc(n.Subnodes, other.Subnodes, InputNode.Equal)
}
func (n InputNode) String() string {
if len(n.Subnodes) == 0 {
if n.Type == "" {
return "string"
}
return n.Type
}
var buf strings.Builder
buf.WriteString("struct {\n")
for _, sub := range n.Subnodes {
buf.WriteString(" ")
buf.Write(sub.Name)
buf.WriteString(" ")
buf.WriteString(sub.String())
buf.WriteString("\n")
}
buf.WriteString(" }")
return buf.String()
}
func (ii *InputStruct) insertVar(node coarse.Token, content []byte, c caseconv.Case, typeCache map[string]string) {
varInfo := node.GetVar(content, typeCache)
if len(varInfo.Path) == 0 {
return
}
// Insert all path parts and set their types based on the cache
ii.insertVarWithCache(varInfo.Path, varInfo.Type, c, typeCache)
}
func (ii *InputStruct) insertVarWithCache(path [][]byte, typ string, c caseconv.Case, typeCache map[string]string) {
if len(path) == 0 {
return
}
nn := c.BytCase(path[0])
idx := slices.IndexFunc(ii.TopLevel, func(n InputNode) bool {
return bytes.Equal(n.Name, nn)
})
if idx == -1 {
newNode := InputNode{Name: nn}
ii.TopLevel = append(ii.TopLevel, newNode)
idx = len(ii.TopLevel) - 1
}
if len(path) == 1 {
// This is a leaf node, set its type from the cache
pathKey := string(bytes.Join(path, []byte(".")))
if cachedType, exists := typeCache[pathKey]; exists && cachedType != "" {
ii.TopLevel[idx].Type = cachedType
} else if ii.TopLevel[idx].Type == "" {
ii.TopLevel[idx].Type = typ
}
return
}
// Check if this intermediate node should be a struct
pathKey := string(bytes.Join(path[:1], []byte(".")))
if cachedType, exists := typeCache[pathKey]; exists && cachedType == "struct" {
ii.TopLevel[idx].Type = "struct"
}
// We're adding nested fields
ii.TopLevel[idx].insertMultiLevelVarWithCache(path[1:], typ, c, typeCache, path[:1])
}
func (n *InputNode) insertMultiLevelVarWithCache(path [][]byte, typ string, c caseconv.Case, typeCache map[string]string, parentPath [][]byte) {
if len(path) == 0 {
slog.Debug("0 len name reached")
return
}
nn := c.BytCase(path[0])
idx := slices.IndexFunc(n.Subnodes, func(n InputNode) bool {
return bytes.Equal(n.Name, nn)
})
if idx == -1 {
newNode := InputNode{Name: nn}
n.Subnodes = append(n.Subnodes, newNode)
idx = len(n.Subnodes) - 1
}
// Build full path for cache lookup
fullPath := make([][]byte, len(parentPath)+1)
copy(fullPath, parentPath)
fullPath[len(parentPath)] = path[0]
if len(path) == 1 {
// This is a leaf node, set its type from the cache or the provided type
pathKey := string(bytes.Join(fullPath, []byte(".")))
if cachedType, exists := typeCache[pathKey]; exists && cachedType != "" {
n.Subnodes[idx].Type = cachedType
} else if n.Subnodes[idx].Type == "" {
n.Subnodes[idx].Type = typ
}
return
}
// Check if this intermediate node should be a struct
pathKey := string(bytes.Join(fullPath, []byte(".")))
if cachedType, exists := typeCache[pathKey]; exists && cachedType == "struct" {
n.Subnodes[idx].Type = "struct"
}
// We're adding nested fields
n.Subnodes[idx].insertMultiLevelVarWithCache(path[1:], typ, c, typeCache, fullPath)
}
func (p Prompt) GetInputs(content []byte, c caseconv.Case) (ii InputStruct, err error) {
typeCache := make(map[string]string)
allPaths := make(map[string]bool)
// First pass: collect all variable paths to understand the complete structure
for _, node := range p.Nodes {
switch node.Kind {
case coarse.Var, coarse.OptionalBlock:
varInfo := node.GetVar(content, typeCache)
if len(varInfo.Path) > 0 {
pathKey := string(bytes.Join(varInfo.Path, []byte(".")))
allPaths[pathKey] = true
// Store explicit type information
if varInfo.Type != "" && varInfo.Type != "string" {
typeCache[pathKey] = varInfo.Type
}
}
}
}
// Analyze paths to determine which should be structs
inferStructTypes(allPaths, typeCache)
// Second pass: build the struct with complete type information
for _, node := range p.Nodes {
switch node.Kind {
case coarse.Var, coarse.OptionalBlock:
ii.insertVar(node, content, c, typeCache)
}
}
// Post-process: set type to "struct" for nodes that have subnodes
for i := range ii.TopLevel {
ii.TopLevel[i].setStructTypes()
}
return
}
// inferStructTypes analyzes all variable paths to determine which should be struct types
// A path should be a struct if there are other paths that are extensions of it
func inferStructTypes(allPaths map[string]bool, typeCache map[string]string) {
for path := range allPaths {
// Check if this path has any children (i.e., other paths that start with this path + ".")
hasChildren := false
pathPrefix := path + "."
for otherPath := range allPaths {
if otherPath != path && strings.HasPrefix(otherPath, pathPrefix) {
hasChildren = true
break
}
}
// If this path has children and doesn't already have a non-struct type, mark it as struct
if hasChildren {
if existingType, exists := typeCache[path]; !exists || existingType == "" || existingType == "string" {
typeCache[path] = "struct"
}
}
}
}
// setStructTypes recursively sets the type to "struct" for nodes that have subnodes
func (n *InputNode) setStructTypes() {
if len(n.Subnodes) > 0 {
n.Type = "struct"
}
for i := range n.Subnodes {
n.Subnodes[i].setStructTypes()
}
}
func (p1 Prompt) Equal(p2 Prompt) bool {
if p1.Title != p2.Title || len(p1.Nodes) != len(p2.Nodes) {
return false
}
for i, node := range p1.Nodes {
if node != p2.Nodes[i] {
return false
}
}
return true
}
func NewFile(name string, content []byte) (f File, err error) {
if !strings.HasSuffix(name, ".af") {
err = fmt.Errorf("file does not have .af extension: %s", name)
return
}
tokens, err := token.Tokenize(content)
if err != nil {
return
}
f.Name = strings.TrimSuffix(name, ".af")
f.Content = content
f.Prompts, err = newPrompts(tokens, content)
return
}
func newPrompts(tokens token.Slice, content []byte) (prompts []Prompt, err error) {
// Convert granular tokens to coarse tokens for AST compatibility
coarseTokens := coarse.Convert(tokens, content)
for _, t := range coarseTokens {
if t.Kind == coarse.Title {
prompts = append(prompts, Prompt{Title: t})
} else if len(prompts) == 0 {
prompts = append(prompts, Prompt{Nodes: []coarse.Token{t}})
} else {
prompts[len(prompts)-1].Nodes = append(prompts[len(prompts)-1].Nodes, t)
}
}
return
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errs
import (
"unique"
)
// TODO INTEGRATE WITH SLOG.ATTR
func New[M ~string](msg M) Error {
return Error{
msg: unique.Make(message(msg)),
}
}
// TODO USE ERROR STACK
func (e Error) Error() string {
return e.Msg().Error()
}
type Error struct {
msg unique.Handle[message]
stack []fmtErr
}
func (e Error) Msg() message {
return e.msg.Value()
}
type message string
func (m message) String() string {
return string(m)
}
func (m message) Error() string {
return string(m)
}
type fmtErr struct {
err error
msg string
args []any
}
// TODO CAPTURE RUNTIME.CALLER FOR ALL BELOW
func (e Error) S(msg string) Error {
e.stack = append(e.stack, fmtErr{msg: msg})
return e
}
func (e Error) F(msg string, args ...any) Error {
e.stack = append(e.stack, fmtErr{msg: msg, args: args})
return e
}
func (e Error) E(err error) Error {
e.stack = append(e.stack, fmtErr{err: err})
return e
}
func (e Error) ES(err error, msg string) Error {
e.stack = append(e.stack, fmtErr{err: err, msg: msg})
return e
}
func (e Error) EF(err error, fmt string, args ...any) Error {
e.stack = append(e.stack, fmtErr{err: err, msg: fmt, args: args})
return e
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gogen
import (
"bytes"
"fmt"
"go/format"
"io"
"slices"
"strconv"
"strings"
"github.com/omniaura/agentflow/cfg"
"github.com/omniaura/agentflow/pkg/ast"
"github.com/omniaura/agentflow/pkg/gen"
"github.com/omniaura/agentflow/pkg/gonames"
"github.com/omniaura/agentflow/pkg/token/coarse"
"github.com/peyton-spencer/caseconv"
"github.com/peyton-spencer/caseconv/bytcase"
)
func GenInputStructs(w io.Writer, f ast.File) error {
var buf bytes.Buffer
// Track generated struct names to avoid duplicates
generatedStructs := make(map[string]bool)
for _, p := range f.Prompts {
inputs, err := p.GetInputs(f.Content, caseconv.CaseCamel)
if err != nil {
return err
}
// Generate embedded structs first
for _, node := range inputs.TopLevel {
if len(node.Subnodes) > 0 {
structName := string(node.Name)
if !generatedStructs[structName] {
generateEmbeddedStruct(&buf, node)
buf.WriteRune('\n')
generatedStructs[structName] = true
}
}
}
}
_, err := buf.WriteTo(w)
return err
}
func generateEmbeddedStruct(buf *bytes.Buffer, node ast.InputNode) {
buf.WriteString("type ")
buf.Write(node.Name)
if len(node.Subnodes) == 0 {
buf.WriteString(" struct{}")
buf.WriteString("\n")
return
}
buf.WriteString(" struct {\n")
for _, subnode := range node.Subnodes {
buf.WriteString("\t")
buf.Write(subnode.Name)
buf.WriteString(" ")
if len(subnode.Subnodes) > 0 {
buf.Write(subnode.Name)
} else {
buf.WriteString("string")
}
buf.WriteString("\n")
}
buf.WriteString("}\n")
}
func GenFile(w io.Writer, f ast.File, dirName string) error {
packageName := gonames.CleanPackageName(dirName)
var buf bytes.Buffer
fmt.Fprintf(&buf, "// Code generated by agentflow v%s; DO NOT EDIT.\n\n", cfg.Version)
buf.WriteString("package ")
buf.WriteString(packageName)
buf.WriteString("\n\n")
// Collect required imports for all prompts
imports := map[string]bool{"strings": false, "strconv": false}
for _, p := range f.Prompts {
typeCache := make(map[string]string)
for _, t := range p.Nodes {
switch t.Kind {
case coarse.Var:
vi := t.GetVar(f.Content, typeCache)
switch vi.Type {
case "int", "bool", "float32", "float64":
imports["strconv"] = true
}
fallthrough
case coarse.OptionalBlock:
// Call GetVar to populate type cache for later Var tokens
t.GetVar(f.Content, typeCache)
imports["strings"] = true
}
}
}
// Emit import block if needed
importList := []string{}
for k, v := range imports {
if v {
importList = append(importList, k)
}
}
if len(importList) > 0 {
slices.Sort(importList)
buf.WriteString("import (\n")
for _, imp := range importList {
buf.WriteString("\t\"")
buf.WriteString(imp)
buf.WriteString("\"\n")
}
buf.WriteString(")\n\n")
}
if len(f.Prompts) == 0 {
return gen.ErrNoPrompts
}
for i, p := range f.Prompts {
if p.Title.Kind == coarse.Unset && len(f.Prompts) > 1 {
return gen.ErrMissingTitle.F("index: %d", i)
}
// Get the struct name
var structName []byte
if p.Title.Kind == coarse.Title {
// Extract just the title text, skipping ".title " prefix
titleContent := p.Title.Get(f.Content)
if len(titleContent) > 7 && string(titleContent[:7]) == ".title " {
// Skip ".title " prefix and get just the title text
titleText := titleContent[7:]
structName = bytcase.ToCamel(titleText)
} else {
structName = bytcase.ToCamel(titleContent)
}
} else {
structName = bytcase.ToCamel([]byte(f.Name))
}
// Generate the struct
inputs, err := p.GetInputs(f.Content, caseconv.CaseCamel)
if err != nil {
return err
}
writeRootStruct(&buf, structName, inputs)
// Generate IsZero methods for nested structs
generateIsZeroMethods(&buf, structName, inputs)
// Generate the String method
buf.WriteString("func (input *")
buf.Write(structName)
buf.WriteString(") String() string {\n")
vars, _ := p.Vars(f.Content, caseconv.CaseCamel)
hasVariables := len(vars) > 0
// Also check for conditionals or any complex tokens that need processing
hasComplexTokens := false
for _, t := range p.Nodes {
if t.Kind == coarse.OptionalBlock || t.Kind == coarse.ElseBlock || t.Kind == coarse.EndTag {
hasComplexTokens = true
break
}
}
if hasVariables || hasComplexTokens {
stringTemplateWithStruct(&buf, p.Nodes, f.Content, inputs)
} else {
stringTemplate(&buf, p.Nodes, f.Content)
}
if i < len(f.Prompts)-1 {
buf.WriteRune('\n')
}
}
outputBytes := buf.Bytes()
formattedBytes, err := format.Source(outputBytes)
if err != nil {
return fmt.Errorf("failed to format code: %w", err)
}
_, err = w.Write(formattedBytes)
return err
}
// generateIsZeroMethods generates IsXxxZero() methods for nested struct fields
func generateIsZeroMethods(buf *bytes.Buffer, structName []byte, inputs ast.InputStruct) {
generateIsZeroMethodsRecursive(buf, structName, inputs.TopLevel, []string{})
}
func generateIsZeroMethodsRecursive(buf *bytes.Buffer, structName []byte, nodes []ast.InputNode, fieldPath []string) {
for _, node := range nodes {
if len(node.Subnodes) > 0 && node.Type == "struct" {
// Generate isZero method for this nested struct (private)
currentPath := append(fieldPath, string(node.Name))
methodName := "is" + strings.Join(currentPath, "") + "Zero"
buf.WriteString("func (input *")
buf.Write(structName)
buf.WriteString(") ")
buf.WriteString(methodName)
buf.WriteString("() bool {\n")
// Build the field access path
fieldAccess := "input"
for _, part := range currentPath {
fieldAccess += "." + part
}
// Generate zero value checks for all fields
buf.WriteString("\treturn ")
for i, subnode := range node.Subnodes {
if i > 0 {
buf.WriteString(" &&\n\t\t")
}
fieldRef := fieldAccess + "." + string(subnode.Name)
// Check if this subnode is a struct (has subnodes)
if len(subnode.Subnodes) > 0 {
// Use the isZero method for struct types
currentPathForSubnode := append(currentPath, string(subnode.Name))
subnodeMethodName := "is" + strings.Join(currentPathForSubnode, "") + "Zero"
buf.WriteString("input." + subnodeMethodName + "()")
} else {
switch subnode.Type {
case "bool":
buf.WriteString("!" + fieldRef)
case "int", "float32", "float64":
buf.WriteString(fieldRef + " == 0")
default: // string and other types
buf.WriteString(fieldRef + " == \"\"")
}
}
}
buf.WriteString("\n}\n\n")
// Recursively generate methods for deeper nested structs
generateIsZeroMethodsRecursive(buf, structName, node.Subnodes, currentPath)
}
}
}
func writeRootStruct(buf *bytes.Buffer, structName []byte, inputs ast.InputStruct) {
buf.WriteString("type ")
buf.Write(structName)
if len(inputs.TopLevel) == 0 {
buf.WriteString(" struct{}\n\n")
return
}
buf.WriteString(" struct {\n")
for _, node := range inputs.TopLevel {
if len(node.Subnodes) > 0 && (node.Type == "struct" || node.Type == "") {
// Generate the embedded struct inline
buf.WriteString("\t")
buf.Write(node.Name)
buf.WriteString(" struct {\n")
generateStructFields(buf, node.Subnodes, 2)
buf.WriteString("\t}\n")
} else {
buf.WriteString("\t")
buf.Write(node.Name)
buf.WriteString(" ")
buf.WriteString(goTypeFor(node.Type))
buf.WriteString("\n")
}
}
buf.WriteString("}\n\n")
}
func generateStructFields(buf *bytes.Buffer, nodes []ast.InputNode, indent int) {
for _, node := range nodes {
for range indent {
buf.WriteString("\t")
}
buf.Write(node.Name)
buf.WriteString(" ")
if len(node.Subnodes) > 0 && (node.Type == "struct" || node.Type == "") {
buf.WriteString("struct {\n")
generateStructFields(buf, node.Subnodes, indent+1)
for range indent {
buf.WriteString("\t")
}
buf.WriteString("}")
} else {
buf.WriteString(goTypeFor(node.Type))
}
buf.WriteString("\n")
}
}
// goTypeFor returns the Go type for a given type string (default: string)
func goTypeFor(typ string) string {
switch typ {
case "int":
return "int"
case "bool":
return "bool"
case "float32":
return "float32"
case "float64":
return "float64"
case "struct":
return "struct{}" // fallback, should not be used for leaf
case "":
return "string"
default:
return "string"
}
}
func stringTemplate(buf *bytes.Buffer, toks []coarse.Token, content []byte) {
var fullContent bytes.Buffer
for _, t := range toks {
fullContent.Write(t.Get(content))
}
buf.WriteString("\treturn ")
buf.WriteString(strconv.Quote(fullContent.String()))
buf.WriteString("\n}\n")
}
func stringTemplateWithStruct(buf *bytes.Buffer, toks []coarse.Token, content []byte, inputs ast.InputStruct) {
buf.WriteString("\tvar b strings.Builder\n")
// Create type cache to track variable types
typeCache := make(map[string]string)
// Create mapping from variable paths to local variable names for numeric variables
numericVarMap := make(map[string]string)
var varDecls []string
varCounter := 0
// Pre-declare string conversions for all stringable variables (they're always used twice: length + output)
for _, t := range toks {
switch t.Kind {
case coarse.OptionalBlock:
t.GetVar(content, typeCache) // populate type cache for later Var tokens
case coarse.Var:
vi := t.GetVar(content, typeCache)
pathKey := string(bytes.Join(vi.Path, []byte(".")))
if _, exists := numericVarMap[pathKey]; !exists {
switch vi.Type {
case "int":
localVar := "var" + strconv.Itoa(varCounter)
numericVarMap[pathKey] = localVar
varDecls = append(varDecls, localVar+" := strconv.Itoa("+varFieldAccess(vi.Path)+")")
varCounter++
case "float32":
localVar := "var" + strconv.Itoa(varCounter)
numericVarMap[pathKey] = localVar
varDecls = append(varDecls, localVar+" := strconv.FormatFloat(float64("+varFieldAccess(vi.Path)+"), 'g', -1, 32)")
varCounter++
case "float64":
localVar := "var" + strconv.Itoa(varCounter)
numericVarMap[pathKey] = localVar
varDecls = append(varDecls, localVar+" := strconv.FormatFloat("+varFieldAccess(vi.Path)+", 'g', -1, 64)")
varCounter++
case "bool":
localVar := "var" + strconv.Itoa(varCounter)
numericVarMap[pathKey] = localVar
varDecls = append(varDecls, localVar+" := strconv.FormatBool("+varFieldAccess(vi.Path)+")")
varCounter++
}
}
}
}
// Emit variable declarations
for _, decl := range varDecls {
buf.WriteString("\t")
buf.WriteString(decl)
buf.WriteString("\n")
}
// First pass: Generate length calculation code
buf.WriteString("\tlength := 0\n")
generateLengthCalculation(buf, toks, content, typeCache, numericVarMap, inputs)
// Grow the buffer
buf.WriteString("\tb.Grow(length)\n")
// Second pass: Generate the actual string building code
generateStringBuilding(buf, toks, content, typeCache, numericVarMap, inputs)
buf.WriteString("\treturn b.String()\n}\n")
}
func generateLengthCalculation(buf *bytes.Buffer, toks []coarse.Token, content []byte, typeCache map[string]string, numericVarMap map[string]string, inputs ast.InputStruct) {
conditionalStack := []bool{} // Track nested conditionals
indentLevel := 1
for _, t := range toks {
switch t.Kind {
case coarse.Var:
vi := t.GetVar(content, typeCache)
writeIndent(buf, indentLevel)
buf.WriteString("length += ")
pathKey := string(bytes.Join(vi.Path, []byte(".")))
switch vi.Type {
case "int", "float32", "float64", "bool":
if localVar, exists := numericVarMap[pathKey]; exists {
buf.WriteString("len(" + localVar + ")")
} else {
// Variable not pre-declared (only used in conditionals), calculate length inline
switch vi.Type {
case "int":
buf.WriteString("len(strconv.Itoa(" + varFieldAccess(vi.Path) + "))")
case "float32":
buf.WriteString("len(strconv.FormatFloat(float64(" + varFieldAccess(vi.Path) + "), 'g', -1, 32))")
case "float64":
buf.WriteString("len(strconv.FormatFloat(" + varFieldAccess(vi.Path) + ", 'g', -1, 64))")
case "bool":
buf.WriteString("5") // max length for "false"
}
}
default:
buf.WriteString("len(" + varFieldAccess(vi.Path) + ")")
}
buf.WriteString("\n")
case coarse.OptionalBlock:
conditionalStack = append(conditionalStack, true)
indentLevel++
vi := t.GetVar(content, typeCache)
writeIndent(buf, indentLevel-1)
buf.WriteString("if ")
// Generate conditional expression
if vi.Operator != "" {
// Custom operator expression
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" ")
buf.WriteString(convertWordOperatorToGo(vi.Operator))
buf.WriteString(" ")
buf.WriteString(vi.Operand) // Use vi.Operand directly (already formatted)
} else {
// Default truthiness check
if isStructPath(vi.Path, inputs) {
// Use the private isZero method for struct types
buf.WriteString("!")
buf.WriteString(generateIsZeroMethodCall(vi.Path))
} else {
switch vi.Type {
case "bool":
// just check if true
buf.WriteString(varFieldAccess(vi.Path))
case "int":
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" != 0")
default:
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" != \"\"")
}
}
}
buf.WriteString(" {\n")
case coarse.ElseBlock:
writeIndent(buf, indentLevel-1)
buf.WriteString("} else {\n")
case coarse.EndTag:
if len(conditionalStack) > 0 {
conditionalStack = conditionalStack[:len(conditionalStack)-1]
indentLevel--
writeIndent(buf, indentLevel)
buf.WriteString("}\n")
}
default:
// static text
textContent := t.Get(content)
if len(textContent) > 0 {
writeIndent(buf, indentLevel)
buf.WriteString("length += ")
buf.WriteString(strconv.Itoa(len(textContent)))
buf.WriteString("\n")
}
}
}
// Close any remaining open conditionals
for len(conditionalStack) > 0 {
conditionalStack = conditionalStack[:len(conditionalStack)-1]
indentLevel--
writeIndent(buf, indentLevel)
buf.WriteString("}\n")
}
}
func generateStringBuilding(buf *bytes.Buffer, toks []coarse.Token, content []byte, typeCache map[string]string, numericVarMap map[string]string, inputs ast.InputStruct) {
conditionalStack := []bool{} // Track nested conditionals
// Buffer for static text segments
var staticBuf []byte
flushStatic := func() {
if len(staticBuf) == 0 {
return
}
buf.WriteString("\tb.WriteString(")
writeQuotedString(buf, staticBuf)
buf.WriteString(")\n")
staticBuf = staticBuf[:0]
}
for _, t := range toks {
switch t.Kind {
case coarse.Var:
flushStatic()
// Add proper indentation based on nesting level
for range len(conditionalStack) {
buf.WriteRune('\t')
}
vi := t.GetVar(content, typeCache)
buf.WriteString("\tb.WriteString(")
pathKey := string(bytes.Join(vi.Path, []byte(".")))
switch vi.Type {
case "int", "float32", "float64", "bool":
if localVar, exists := numericVarMap[pathKey]; exists {
buf.WriteString(localVar)
} else {
// Variable not pre-declared, format inline
switch vi.Type {
case "int":
buf.WriteString("strconv.Itoa(" + varFieldAccess(vi.Path) + ")")
case "float32":
buf.WriteString("strconv.FormatFloat(float64(" + varFieldAccess(vi.Path) + "), 'g', -1, 32)")
case "float64":
buf.WriteString("strconv.FormatFloat(" + varFieldAccess(vi.Path) + ", 'g', -1, 64)")
case "bool":
buf.WriteString("strconv.FormatBool(" + varFieldAccess(vi.Path) + ")")
}
}
default:
buf.WriteString(varFieldAccess(vi.Path))
}
buf.WriteString(")\n")
case coarse.OptionalBlock:
flushStatic()
conditionalStack = append(conditionalStack, true)
// Add proper indentation for the if statement
for range len(conditionalStack) - 1 {
buf.WriteRune('\t')
}
vi := t.GetVar(content, typeCache)
buf.WriteString("\tif ")
// Generate conditional expression
if vi.Operator != "" {
// Custom operator expression
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" ")
buf.WriteString(convertWordOperatorToGo(vi.Operator))
buf.WriteString(" ")
buf.WriteString(vi.Operand) // Use vi.Operand directly (already formatted)
} else {
// Default truthiness check
if isStructPath(vi.Path, inputs) {
// Use the private isZero method for struct types
buf.WriteString("!")
buf.WriteString(generateIsZeroMethodCall(vi.Path))
} else {
switch vi.Type {
case "bool":
// just check if true
buf.WriteString(varFieldAccess(vi.Path))
case "int":
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" != 0")
default:
buf.WriteString(varFieldAccess(vi.Path))
buf.WriteString(" != \"\"")
}
}
}
buf.WriteString(" {\n")
case coarse.ElseBlock:
flushStatic()
// Add proper indentation for the else statement
for range len(conditionalStack) - 1 {
buf.WriteRune('\t')
}
buf.WriteString("\t} else {\n")
case coarse.EndTag:
flushStatic()
if len(conditionalStack) > 0 {
conditionalStack = conditionalStack[:len(conditionalStack)-1]
// Add proper indentation for the closing brace
for range len(conditionalStack) {
buf.WriteRune('\t')
}
buf.WriteString("\t}\n")
}
default:
// static text
textContent := t.Get(content)
if bytes.Equal(textContent, []byte("\n")) {
flushStatic()
for range len(conditionalStack) {
buf.WriteRune('\t')
}
buf.WriteString("\tb.WriteRune('\\n')\n")
} else if len(textContent) > 0 {
staticBuf = append(staticBuf, textContent...)
}
}
}
flushStatic()
// Close any remaining open conditionals
for len(conditionalStack) > 0 {
conditionalStack = conditionalStack[:len(conditionalStack)-1]
// Add proper indentation for the closing brace
for range len(conditionalStack) {
buf.WriteRune('\t')
}
buf.WriteString("\t}\n")
}
}
func writeIndent(buf *bytes.Buffer, level int) {
for i := 0; i < level; i++ {
buf.WriteRune('\t')
}
}
// writeQuotedString writes a properly escaped quoted string literal
func writeQuotedString(buf *bytes.Buffer, content []byte) {
buf.WriteRune('"')
for _, b := range content {
switch b {
case '"':
buf.WriteString(`\"`)
case '\\':
buf.WriteString(`\\`)
case '\n':
buf.WriteString(`\n`)
case '\r':
buf.WriteString(`\r`)
case '\t':
buf.WriteString(`\t`)
default:
buf.WriteByte(b)
}
}
buf.WriteRune('"')
}
// convertWordOperatorToGo converts word operators to Go operators
func convertWordOperatorToGo(wordOp string) string {
switch wordOp {
case "gte":
return ">="
case "lte":
return "<="
case "gt":
return ">"
case "lt":
return "<"
case "eq":
return "=="
case "ne":
return "!="
default:
return wordOp // fallback
}
}
// varFieldAccess returns the Go field access string for a variable path
func varFieldAccess(path [][]byte) string {
var buf strings.Builder
buf.WriteString("input.")
for i, part := range path {
buf.Write(bytcase.ToCamel(part))
if i < len(path)-1 {
buf.WriteRune('.')
}
}
return buf.String()
}
// generateIsZeroMethodCall generates a call to the private isZero method for a struct field
func generateIsZeroMethodCall(path [][]byte) string {
var buf strings.Builder
buf.WriteString("input.is")
for _, part := range path {
buf.Write(bytcase.ToCamel(part))
}
buf.WriteString("Zero()")
return buf.String()
}
// isStructPath checks if a given variable path represents a struct field in the InputStruct
func isStructPath(path [][]byte, inputs ast.InputStruct) bool {
if len(path) == 0 {
return false
}
// Find the top-level node
topLevelName := bytcase.ToCamel(path[0])
var currentNode *ast.InputNode
for i := range inputs.TopLevel {
if bytes.Equal(inputs.TopLevel[i].Name, topLevelName) {
currentNode = &inputs.TopLevel[i]
break
}
}
if currentNode == nil {
return false
}
// Traverse the path
for i := 1; i < len(path); i++ {
found := false
fieldName := bytcase.ToCamel(path[i])
for j := range currentNode.Subnodes {
if bytes.Equal(currentNode.Subnodes[j].Name, fieldName) {
currentNode = ¤tNode.Subnodes[j]
found = true
break
}
}
if !found {
return false
}
}
// Check if this final node is a struct (has subnodes)
return len(currentNode.Subnodes) > 0
}
package gonames
import "strings"
var replacerPackageName = strings.NewReplacer(" ", "", "-", "", "_", "")
// CleanPackageName removes all spaces, hyphens, and underscores from a string and converts it to lowercase.
func CleanPackageName(name string) string {
return strings.ToLower(replacerPackageName.Replace(name))
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logger
import (
"log/slog"
"github.com/omniaura/agentflow/cfg"
)
func Setup() {
SetupLevel(cfg.LogLevel())
}
func SetupLevel(lvl slog.Level) {
slog.SetLogLoggerLevel(lvl)
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lsp
import (
"bytes"
"fmt"
"log/slog"
"strings"
"sync"
"time"
"github.com/omniaura/agentflow/pkg/ast"
"github.com/omniaura/agentflow/pkg/token"
"github.com/omniaura/agentflow/pkg/token/coarse"
"github.com/omniaura/agentflow/pkg/token/kind"
protocol "github.com/tliron/glsp/protocol_3_16"
)
// Document represents a text document being managed by the LSP server
type Document struct {
URI string
Version protocol.Integer
Content []byte
Lines [][]byte
// Parsed AST and tokens from AgentFlow parser
AST ast.File
Tokens token.Slice
// Analysis results
Variables []Variable
Titles []Title
Errors []ParseError
}
// Variable represents a variable found in the document
type Variable struct {
Name string
Type string
Range Range
DotPath []string // For nested variables like user.name
IsTyped bool // Whether type was explicitly declared
}
// Title represents a .title directive
type Title struct {
Name string
Range Range
}
// ParseError represents a syntax or semantic error
type ParseError struct {
Range Range
Message string
Code string
}
// DocumentManager manages all open documents
type DocumentManager struct {
mu sync.RWMutex
documents map[string]*Document
}
// NewDocumentManager creates a new document manager
func NewDocumentManager() *DocumentManager {
slog.Info("Creating new document manager")
return &DocumentManager{
documents: make(map[string]*Document),
}
}
// OpenDocument opens and parses a new document
func (dm *DocumentManager) OpenDocument(document protocol.TextDocumentItem) (*Document, error) {
start := time.Now()
uri := document.URI
slog.Info("Opening document",
"uri", uri,
"languageId", document.LanguageID,
"version", document.Version,
"contentLength", len(document.Text),
"lineCount", strings.Count(document.Text, "\n")+1)
dm.mu.Lock()
defer dm.mu.Unlock()
// Check if document is already open
if existing, exists := dm.documents[uri]; exists {
slog.Warn("Document already open, replacing",
"uri", uri,
"existingVersion", existing.Version,
"newVersion", document.Version)
}
docBytes := []byte(document.Text)
doc := &Document{
URI: uri,
Version: document.Version,
Content: docBytes,
Lines: bytes.Split(docBytes, []byte("\n")),
}
slog.Debug("Document content split into lines",
"uri", uri,
"lineCount", len(doc.Lines))
// Parse the document using AgentFlow parser
parseStart := time.Now()
if err := doc.parse(); err != nil {
slog.Error("Parse failed for document",
"uri", uri,
"error", err,
"parseDuration", time.Since(parseStart))
// Even if parsing fails, we still want to track the document
doc.Errors = append(doc.Errors, ParseError{
Range: Range{
Start: Position{Line: 0, Character: 0},
End: Position{Line: 0, Character: 0},
},
Message: fmt.Sprintf("Parse error: %v", err),
Code: "parse_error",
})
} else {
slog.Info("Document parsed successfully",
"uri", uri,
"parseDuration", time.Since(parseStart),
"variableCount", len(doc.Variables),
"titleCount", len(doc.Titles),
"errorCount", len(doc.Errors),
"tokenCount", len(doc.Tokens))
}
dm.documents[doc.URI] = doc
totalDuration := time.Since(start)
slog.Info("Document opened successfully",
"uri", uri,
"totalDuration", totalDuration,
"totalDocuments", len(dm.documents))
return doc, nil
}
// UpdateDocument updates an existing document
func (dm *DocumentManager) UpdateDocument(uri string, version protocol.Integer, changes []TextDocumentContentChangeEvent) (*Document, error) {
start := time.Now()
slog.Info("Updating document",
"uri", uri,
"version", version,
"changeCount", len(changes))
dm.mu.Lock()
defer dm.mu.Unlock()
doc, exists := dm.documents[uri]
if !exists {
slog.Error("Document not found for update", "uri", uri)
return nil, fmt.Errorf("document not found: %s", uri)
}
oldVersion := doc.Version
oldContentLength := len(doc.Content)
slog.Debug("Current document state",
"uri", uri,
"oldVersion", oldVersion,
"newVersion", version,
"oldContentLength", oldContentLength,
"oldLineCount", len(doc.Lines))
// Apply changes to document content
for i, change := range changes {
changeStart := time.Now()
if change.Range == nil {
// Full document replacement
slog.Debug("Applying full document replacement",
"uri", uri,
"changeIndex", i,
"newContentLength", len(change.Text))
doc.Content = []byte(change.Text)
} else {
// Incremental change - for now, we'll implement full replacement
// TODO: Implement proper incremental updates
slog.Debug("Applying incremental change (as full replacement)",
"uri", uri,
"changeIndex", i,
"rangeStart", change.Range.Start,
"rangeEnd", change.Range.End,
"newContentLength", len(change.Text))
doc.Content = []byte(change.Text)
}
slog.Debug("Change applied",
"uri", uri,
"changeIndex", i,
"changeDuration", time.Since(changeStart))
}
doc.Version = version
doc.Lines = bytes.Split([]byte(doc.Content), []byte("\n"))
slog.Debug("Document content updated",
"uri", uri,
"newContentLength", len(doc.Content),
"newLineCount", len(doc.Lines),
"contentLengthDelta", len(doc.Content)-oldContentLength)
// Re-parse the document
parseStart := time.Now()
doc.Variables = nil
doc.Titles = nil
doc.Errors = nil
slog.Debug("Re-parsing document after update", "uri", uri)
if err := doc.parse(); err != nil {
slog.Error("Re-parse failed for updated document",
"uri", uri,
"error", err,
"parseDuration", time.Since(parseStart))
doc.Errors = append(doc.Errors, ParseError{
Range: Range{
Start: Position{Line: 0, Character: 0},
End: Position{Line: 0, Character: 0},
},
Message: fmt.Sprintf("Parse error: %v", err),
Code: "parse_error",
})
} else {
slog.Info("Document re-parsed successfully",
"uri", uri,
"parseDuration", time.Since(parseStart),
"variableCount", len(doc.Variables),
"titleCount", len(doc.Titles),
"errorCount", len(doc.Errors),
"tokenCount", len(doc.Tokens))
}
totalDuration := time.Since(start)
slog.Info("Document updated successfully",
"uri", uri,
"versionChange", fmt.Sprintf("%d -> %d", oldVersion, version),
"totalDuration", totalDuration)
return doc, nil
}
// CloseDocument removes a document from management
func (dm *DocumentManager) CloseDocument(uri string) {
start := time.Now()
slog.Info("Closing document", "uri", uri)
dm.mu.Lock()
defer dm.mu.Unlock()
if _, exists := dm.documents[uri]; exists {
delete(dm.documents, uri)
slog.Info("Document closed successfully",
"uri", uri,
"remainingDocuments", len(dm.documents),
"duration", time.Since(start))
} else {
slog.Warn("Attempted to close non-existent document", "uri", uri)
}
}
// GetDocument retrieves a document
func (dm *DocumentManager) GetDocument(uri string) (*Document, bool) {
start := time.Now()
dm.mu.RLock()
defer dm.mu.RUnlock()
doc, exists := dm.documents[uri]
duration := time.Since(start)
if exists {
slog.Debug("Document retrieved",
"uri", uri,
"version", doc.Version,
"contentLength", len(doc.Content),
"duration", duration)
} else {
slog.Debug("Document not found",
"uri", uri,
"availableDocuments", len(dm.documents),
"duration", duration)
}
return doc, exists
}
// parse parses the document content using AgentFlow parser
func (d *Document) parse() error {
start := time.Now()
slog.Debug("Starting document parse",
"uri", d.URI,
"contentLength", len(d.Content))
// Extract filename from URI for the parser
filename := extractFilename(d.URI)
slog.Debug("Extracted filename for parsing",
"uri", d.URI,
"filename", filename)
// Parse using AgentFlow's existing parser
astStart := time.Now()
astFile, err := ast.NewFile(filename, []byte(d.Content))
if err != nil {
slog.Error("AST parsing failed",
"uri", d.URI,
"filename", filename,
"error", err,
"astDuration", time.Since(astStart))
return err
}
slog.Debug("AST parsing completed",
"uri", d.URI,
"astDuration", time.Since(astStart),
"promptCount", len(astFile.Prompts))
d.AST = astFile
// Tokenize for detailed analysis
tokenStart := time.Now()
tokens, err := token.Tokenize([]byte(d.Content))
if err != nil {
slog.Error("Tokenization failed",
"uri", d.URI,
"error", err,
"tokenDuration", time.Since(tokenStart))
return err
}
slog.Debug("Tokenization completed",
"uri", d.URI,
"tokenDuration", time.Since(tokenStart),
"tokenCount", len(tokens))
d.Tokens = tokens
// Extract variables and titles from the AST/tokens
extractStart := time.Now()
d.extractVariables()
variablesDuration := time.Since(extractStart)
titleStart := time.Now()
d.extractTitles()
titlesDuration := time.Since(titleStart)
totalDuration := time.Since(start)
slog.Info("Document parsing completed",
"uri", d.URI,
"totalDuration", totalDuration,
"astDuration", time.Since(astStart),
"tokenDuration", time.Since(tokenStart),
"variablesDuration", variablesDuration,
"titlesDuration", titlesDuration,
"variableCount", len(d.Variables),
"titleCount", len(d.Titles))
return nil
}
// extractVariables extracts all variables from the parsed tokens
func (d *Document) extractVariables() {
start := time.Now()
slog.Debug("Extracting variables from tokens",
"uri", d.URI,
"tokenCount", len(d.Tokens))
variableCount := 0
for i, tok := range d.Tokens {
// Process granular tokens for variable analysis
if tok.Kind == kind.VarName {
varStart := time.Now()
slog.Debug("Processing variable token",
"uri", d.URI,
"tokenIndex", i,
"tokenKind", tok.Kind,
"tokenStart", tok.Start,
"tokenEnd", tok.End)
// Extract variable name from granular token
varName := string(tok.Get([]byte(d.Content)))
// Convert byte position to line/character position
startPos := d.byteOffsetToPosition(tok.Start)
endPos := d.byteOffsetToPosition(tok.End)
// Split variable name by dots for path
pathParts := strings.Split(varName, ".")
// Determine type - look for TypeName token after variable name (skip whitespace)
varType := "string" // default
for j := i + 1; j < len(d.Tokens); j++ {
if d.Tokens[j].Kind == kind.TypeName {
varType = string(d.Tokens[j].Get([]byte(d.Content)))
break
} else if d.Tokens[j].Kind != kind.Whitespace {
// Stop if we hit a non-whitespace, non-type token
break
}
}
variable := Variable{
Name: varName,
Type: varType,
DotPath: pathParts,
IsTyped: varType != "" && varType != "string",
Range: Range{
Start: startPos,
End: endPos,
},
}
d.Variables = append(d.Variables, variable)
variableCount++
slog.Debug("Variable extracted",
"uri", d.URI,
"variableName", variable.Name,
"variableType", variable.Type,
"isTyped", variable.IsTyped,
"dotPathLength", len(variable.DotPath),
"extractDuration", time.Since(varStart))
}
}
duration := time.Since(start)
slog.Info("Variable extraction completed",
"uri", d.URI,
"variableCount", variableCount,
"duration", duration)
}
// extractTitles extracts all .title directives from the parsed tokens
func (d *Document) extractTitles() {
start := time.Now()
slog.Debug("Extracting titles from AST",
"uri", d.URI,
"promptCount", len(d.AST.Prompts))
titleCount := 0
for i, prompt := range d.AST.Prompts {
// Process coarse tokens for title analysis
if prompt.Title.Kind == coarse.Title {
titleStart := time.Now()
slog.Debug("Processing title token",
"uri", d.URI,
"promptIndex", i,
"titleStart", prompt.Title.Start,
"titleEnd", prompt.Title.End)
startPos := d.byteOffsetToPosition(prompt.Title.Start)
endPos := d.byteOffsetToPosition(prompt.Title.End)
titleText := string(d.AST.Content[prompt.Title.Start:prompt.Title.End])
title := Title{
Name: titleText,
Range: Range{
Start: startPos,
End: endPos,
},
}
d.Titles = append(d.Titles, title)
titleCount++
slog.Debug("Title extracted",
"uri", d.URI,
"titleName", titleText,
"extractDuration", time.Since(titleStart))
}
}
duration := time.Since(start)
slog.Info("Title extraction completed",
"uri", d.URI,
"titleCount", titleCount,
"duration", duration)
}
// byteOffsetToPosition converts a byte offset to a Position
func (d *Document) byteOffsetToPosition(offset int) Position {
start := time.Now()
if offset < 0 {
slog.Debug("Invalid byte offset (negative)",
"uri", d.URI,
"offset", offset)
return Position{Line: 0, Character: 0}
}
line := 0
character := 0
for i := 0; i < len(d.Content) && i < offset; i++ {
if d.Content[i] == '\n' {
line++
character = 0
} else {
character++
}
}
result := Position{Line: line, Character: character}
duration := time.Since(start)
if debugMode {
slog.Debug("Byte offset converted to position",
"uri", d.URI,
"offset", offset,
"line", line,
"character", character,
"duration", duration)
}
return result
}
// GetVariableAt returns the variable at a specific position
func (d *Document) GetVariableAt(pos Position) *Variable {
start := time.Now()
slog.Debug("Looking for variable at position",
"uri", d.URI,
"line", pos.Line,
"character", pos.Character,
"totalVariables", len(d.Variables))
for i, variable := range d.Variables {
if positionInRange(pos, variable.Range) {
duration := time.Since(start)
slog.Debug("Variable found at position",
"uri", d.URI,
"variableIndex", i,
"variableName", variable.Name,
"variableType", variable.Type,
"searchDuration", duration)
return &variable
}
}
duration := time.Since(start)
slog.Debug("No variable found at position",
"uri", d.URI,
"line", pos.Line,
"character", pos.Character,
"searchDuration", duration)
return nil
}
// GetCompletionItems returns completion suggestions for a given position
func (d *Document) GetCompletionItems(pos Position) []CompletionItem {
start := time.Now()
slog.Debug("Generating completion items",
"uri", d.URI,
"line", pos.Line,
"character", pos.Character)
var items []CompletionItem
// Get current line and character context
if pos.Line >= len(d.Lines) {
slog.Debug("Position beyond document lines",
"uri", d.URI,
"requestedLine", pos.Line,
"maxLine", len(d.Lines)-1)
return items
}
line := d.Lines[pos.Line]
if pos.Character > len(line) {
slog.Debug("Character position beyond line length",
"uri", d.URI,
"line", pos.Line,
"requestedChar", pos.Character,
"lineLength", len(line))
return items
}
// Check if we're inside a variable declaration context
beforeCursor := line[:pos.Character]
slog.Debug("Analyzing context for completion",
"uri", d.URI,
"lineContent", line,
"beforeCursor", beforeCursor)
itemCount := 0
// If we're after "<!" suggest variables
if bytes.Contains(beforeCursor, []byte("<!")) && !bytes.Contains(beforeCursor, []byte(">")) {
slog.Debug("In variable context, suggesting variables", "uri", d.URI)
// Suggest existing variables
seenVars := make(map[string]bool)
for _, variable := range d.Variables {
if !seenVars[variable.Name] {
items = append(items, CompletionItem{
Label: variable.Name,
Kind: protocol.CompletionItemKindVariable,
Detail: variable.Type,
})
seenVars[variable.Name] = true
itemCount++
}
}
slog.Debug("Added variable suggestions",
"uri", d.URI,
"uniqueVariables", len(seenVars))
// Suggest types if we're after a space
if bytes.Contains(beforeCursor, []byte(" ")) {
slog.Debug("In type context, suggesting types", "uri", d.URI)
types := []string{"string", "int", "bool", "float32", "float64"}
for _, typ := range types {
items = append(items, CompletionItem{
Label: typ,
Kind: protocol.CompletionItemKindKeyword,
})
itemCount++
}
}
}
// If we're after "<?" suggest conditional operators
if bytes.Contains(beforeCursor, []byte("<?")) && !bytes.Contains(beforeCursor, []byte(">")) {
slog.Debug("In conditional context, suggesting operators", "uri", d.URI)
operators := []string{"eq", "ne", "gt", "lt", "gte", "lte"}
for _, op := range operators {
items = append(items, CompletionItem{
Label: op,
Kind: protocol.CompletionItemKindOperator,
})
itemCount++
}
}
// Suggest .title at beginning of line
if len(bytes.TrimSpace(beforeCursor)) == 0 || bytes.HasPrefix(bytes.TrimSpace(beforeCursor), []byte(".")) {
slog.Debug("At line start, suggesting directives", "uri", d.URI)
items = append(items, CompletionItem{
Label: ".title",
Kind: protocol.CompletionItemKindKeyword,
InsertText: ".title ",
})
itemCount++
}
duration := time.Since(start)
slog.Info("Completion items generated",
"uri", d.URI,
"itemCount", itemCount,
"duration", duration)
return items
}
// positionInRange checks if a position is within a range
func positionInRange(pos Position, r Range) bool {
if pos.Line < r.Start.Line || pos.Line > r.End.Line {
return false
}
if pos.Line == r.Start.Line && pos.Character < r.Start.Character {
return false
}
if pos.Line == r.End.Line && pos.Character > r.End.Character {
return false
}
return true
}
// extractFilename extracts filename from URI
func extractFilename(uri string) string {
// Simple implementation - in reality, we'd properly parse the URI
parts := strings.Split(uri, "/")
if len(parts) > 0 {
filename := parts[len(parts)-1]
slog.Debug("Filename extracted from URI",
"uri", uri,
"filename", filename)
return filename
}
slog.Debug("Could not extract filename from URI, using default",
"uri", uri,
"defaultFilename", "unknown.af")
return "unknown.af"
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lsp
import (
"log/slog"
"time"
"github.com/omniaura/agentflow/pkg/token/kind"
"github.com/tliron/glsp/protocol_3_16"
)
// generateGranularSemanticTokens creates semantic tokens using the new granular token system
func generateGranularSemanticTokens(doc *Document) []protocol.UInteger {
start := time.Now()
slog.Debug("Generating semantic tokens with granular tokens",
"uri", doc.URI,
"tokenCount", len(doc.Tokens))
// Token type indices (must match the order in server.go capabilities)
tokenTypeMap := map[string]protocol.UInteger{
TokenTypeKeyword: 0, // .title, !, ?, /, else
TokenTypeVariable: 1, // variable names
TokenTypeString: 2, // string values, title text
TokenTypeComment: 3, // comments (unused)
TokenTypeOperator: 4, // eq, gte, lte operators
TokenTypeType: 5, // type annotations
TokenTypeParameter: 6, // int/bool values
TokenTypeDecorator: 7, // .title directive
TokenTypeFunction: 8, // conditional blocks (unused)
TokenTypeProperty: 9, // variable path segments (unused)
TokenTypeTagOpen: 10, // < and >
TokenTypeTagClose: 11, // (same as TagOpen)
}
var semanticTokens []protocol.UInteger
var lastLine protocol.UInteger = 0
var lastChar protocol.UInteger = 0
// Helper function to add a semantic token
addToken := func(line, char, length protocol.UInteger, tokenType string) {
// Calculate deltas
deltaLine := line - lastLine
var deltaChar protocol.UInteger
if deltaLine == 0 {
deltaChar = char - lastChar
} else {
deltaChar = char
}
// Get token type index
tokenTypeIndex, exists := tokenTypeMap[tokenType]
if !exists {
tokenTypeIndex = tokenTypeMap[TokenTypeString] // fallback
}
semanticTokens = append(semanticTokens,
deltaLine, // deltaLine
deltaChar, // deltaStart
length, // length
tokenTypeIndex, // tokenType
0, // tokenModifiers (no modifiers needed)
)
lastLine = line
lastChar = char
slog.Debug("Added semantic token",
"uri", doc.URI,
"line", line,
"char", char,
"length", length,
"tokenType", tokenType,
"tokenTypeIndex", tokenTypeIndex)
}
// Process granular tokens directly
for _, tok := range doc.Tokens {
position := doc.byteOffsetToPosition(tok.Start)
line := protocol.UInteger(position.Line)
char := protocol.UInteger(position.Character)
length := protocol.UInteger(tok.End - tok.Start)
var tokenType string
switch tok.Kind {
case kind.TitleDirective:
tokenType = TokenTypeKeyword // Same color as 'else' and other directives
case kind.TitleText:
tokenType = TokenTypeFunction // Title text should be colored like function names
case kind.OpenBracket:
tokenType = TokenTypeTagOpen
case kind.CloseBracket:
tokenType = TokenTypeTagClose // Properly distinguish open from close brackets
case kind.DirectiveVar, kind.DirectiveCond, kind.DirectiveEnd, kind.DirectiveElse:
tokenType = TokenTypeKeyword
case kind.VarName:
tokenType = TokenTypeVariable
case kind.TypeName:
tokenType = TokenTypeType
case kind.Operator:
tokenType = TokenTypeOperator
case kind.StringValue:
tokenType = TokenTypeParameter // String values in conditionals should be colored like parameters
case kind.IntValue, kind.BoolValue:
tokenType = TokenTypeParameter
case kind.Text:
tokenType = TokenTypeString // Regular text content
case kind.Whitespace:
// Skip whitespace tokens - they don't need semantic highlighting
continue
default:
// Skip unrecognized tokens
continue
}
addToken(line, char, length, tokenType)
}
duration := time.Since(start)
slog.Debug("Generated semantic tokens",
"uri", doc.URI,
"tokenCount", len(semanticTokens)/5,
"rawDataLength", len(semanticTokens),
"duration", duration)
return semanticTokens
}
package lsp
import (
"fmt"
"strings"
"github.com/omniaura/agentflow/pkg/token"
"github.com/omniaura/agentflow/pkg/token/kind"
)
type OperatorInfo struct {
Name string
Description string
Examples []string
}
var operatorDocs = map[string]OperatorInfo{
"eq": {
Name: "eq (equals)",
Description: "Checks if two values are equal. Returns true if the left operand equals the right operand.",
Examples: []string{
`if user.age eq 18`,
`if status eq "active"`,
`if count eq 0`,
},
},
"ne": {
Name: "ne (not equals)",
Description: "Checks if two values are not equal. Returns true if the left operand does not equal the right operand.",
Examples: []string{
`if user.status ne "inactive"`,
`if result ne null`,
`if count ne 0`,
},
},
"gt": {
Name: "gt (greater than)",
Description: "Checks if the left value is greater than the right value. Works with numbers, strings (lexicographic), and dates.",
Examples: []string{
`if user.age gt 21`,
`if temperature gt 32.0`,
`if name gt "apple"`,
},
},
"lt": {
Name: "lt (less than)",
Description: "Checks if the left value is less than the right value. Works with numbers, strings (lexicographic), and dates.",
Examples: []string{
`if user.age lt 65`,
`if temperature lt 0`,
`if priority lt "high"`,
},
},
"gte": {
Name: "gte (greater than or equal)",
Description: "Checks if the left value is greater than or equal to the right value. Returns true if left >= right.",
Examples: []string{
`if user.age gte 18`,
`if score gte 80.0`,
`if version gte "2.0"`,
},
},
"lte": {
Name: "lte (less than or equal)",
Description: "Checks if the left value is less than or equal to the right value. Returns true if left <= right.",
Examples: []string{
`if user.age lte 100`,
`if usage lte limit`,
`if priority lte "medium"`,
},
},
}
func (d *Document) GetOperatorAt(pos Position) *OperatorInfo {
line := int(pos.Line)
character := int(pos.Character)
if line >= len(d.Lines) {
return nil
}
lineText := d.Lines[line]
if character >= len(lineText) {
return nil
}
tokens, _ := token.Tokenize([]byte(lineText))
currentPos := 0
for _, tok := range tokens {
txt := tok.Get(lineText)
tokenStart := currentPos
tokenEnd := currentPos + len(txt)
if character >= tokenStart && character < tokenEnd && tok.Kind == kind.Operator {
if info, exists := operatorDocs[string(txt)]; exists {
return &info
}
}
currentPos = tokenEnd
if len(txt) > 0 {
currentPos++
}
}
return nil
}
func formatOperatorHover(info *OperatorInfo) string {
var content strings.Builder
content.WriteString(fmt.Sprintf("**%s**\n\n", info.Name))
content.WriteString(fmt.Sprintf("%s\n\n", info.Description))
if len(info.Examples) > 0 {
content.WriteString("**Examples:**\n")
for _, example := range info.Examples {
content.WriteString(fmt.Sprintf("```agentflow\n%s\n```\n", example))
}
}
return content.String()
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lsp
import (
"fmt"
"log/slog"
"time"
"github.com/omniaura/agentflow/pkg/ptrconv"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
"github.com/tliron/glsp/server"
)
var (
version = "0.1.0"
handler protocol.Handler
documents *DocumentManager
debugMode bool
)
// NewServer creates a new LSP server using the GLSP framework
func NewServer(debug bool) *server.Server {
debugMode = debug
slog.Info("Creating new LSP server", "debug", debug, "version", version)
documents = NewDocumentManager()
handler = protocol.Handler{
Initialize: initialize,
Initialized: initialized,
Shutdown: shutdown,
SetTrace: setTrace,
TextDocumentDidOpen: textDocumentDidOpen,
TextDocumentDidChange: textDocumentDidChange,
TextDocumentDidClose: textDocumentDidClose,
TextDocumentCompletion: textDocumentCompletion,
TextDocumentHover: textDocumentHover,
TextDocumentDocumentSymbol: textDocumentDocumentSymbol,
TextDocumentSemanticTokensFull: textDocumentSemanticTokensFull,
}
s := server.NewServer(&handler, "AgentFlow LSP", debug)
slog.Info("LSP server created successfully")
return s
}
// LSP Handler Functions
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
start := time.Now()
slog.Info("LSP initialize request received",
"processId", params.ProcessID,
"rootUri", params.RootURI,
"clientName", getClientName(params.ClientInfo))
if debugMode {
slog.Debug("Initialize params", "params", params)
}
capabilities := handler.CreateServerCapabilities()
// Configure our specific capabilities
capabilities.TextDocumentSync = protocol.TextDocumentSyncOptions{
OpenClose: ptrconv.Bool(true),
Change: ptrconv.Int32(protocol.TextDocumentSyncKindFull),
}
capabilities.HoverProvider = ptrconv.Bool(true)
capabilities.CompletionProvider = &protocol.CompletionOptions{
ResolveProvider: ptrconv.Bool(false),
TriggerCharacters: []string{"<", "!", "?", ".", " "},
}
capabilities.DocumentSymbolProvider = ptrconv.Bool(true)
// Configure semantic tokens capability
capabilities.SemanticTokensProvider = protocol.SemanticTokensOptions{
Legend: protocol.SemanticTokensLegend{
TokenTypes: []string{
TokenTypeKeyword, // .title
TokenTypeVariable, // <!variable>
TokenTypeString, // text content
TokenTypeComment, // comments
TokenTypeOperator, // eq, gte, lte operators
TokenTypeType, // type annotations
TokenTypeParameter, // variable parameters
TokenTypeDecorator, // .title prefix
TokenTypeFunction, // conditional blocks
TokenTypeProperty, // variable path segments
TokenTypeTagOpen, // <!, <?, </
TokenTypeTagClose, // >
},
TokenModifiers: []string{
TokenModifierDeclaration,
TokenModifierDefinition,
TokenModifierReadonly,
TokenModifierDocumentation,
},
},
Range: ptrconv.Bool(false), // We'll implement full document only for now
Full: ptrconv.Bool(true),
}
change := protocol.FileOperationRegistrationOptions{
Filters: []protocol.FileOperationFilter{
{
Scheme: ptrconv.Str("file"),
Pattern: protocol.FileOperationPattern{
Glob: "**/*.af",
},
},
},
}
capabilities.Workspace = &protocol.ServerCapabilitiesWorkspace{
FileOperations: &protocol.ServerCapabilitiesWorkspaceFileOperations{
DidCreate: &change,
DidRename: &change,
DidDelete: &change,
},
}
result := protocol.InitializeResult{
Capabilities: capabilities,
ServerInfo: &protocol.InitializeResultServerInfo{
Name: "AgentFlow LSP",
Version: ptrconv.Str(version),
},
}
duration := time.Since(start)
slog.Info("LSP initialize completed", "duration", duration)
if debugMode {
slog.Debug("Initialize result", "capabilities", capabilities)
}
return result, nil
}
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
slog.Info("LSP initialized notification received")
if debugMode {
slog.Debug("Initialized params", "params", params)
}
slog.Info("AgentFlow LSP server is ready to accept requests")
return nil
}
func shutdown(context *glsp.Context) error {
slog.Info("LSP shutdown request received")
protocol.SetTraceValue(protocol.TraceValueOff)
slog.Info("LSP server shutting down")
return nil
}
func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error {
slog.Info("LSP setTrace request received", "value", params.Value)
protocol.SetTraceValue(params.Value)
slog.Info("Trace value set", "value", params.Value)
return nil
}
func textDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
start := time.Now()
uri := params.TextDocument.URI
slog.Info("LSP textDocument/didOpen received",
"uri", uri,
"languageId", params.TextDocument.LanguageID,
"version", params.TextDocument.Version,
"contentLength", len(params.TextDocument.Text))
if debugMode {
slog.Debug("Document open params", "params", params)
}
doc, err := documents.OpenDocument(params.TextDocument)
if err != nil {
slog.Error("Failed to open document",
"uri", uri,
"error", err,
"duration", time.Since(start))
return fmt.Errorf("failed to open document: %w", err)
}
slog.Info("Document opened successfully",
"uri", uri,
"variableCount", len(doc.Variables),
"titleCount", len(doc.Titles),
"errorCount", len(doc.Errors),
"duration", time.Since(start))
// Send diagnostics
diagStart := time.Now()
publishDiagnostics(context, doc)
slog.Debug("Published diagnostics",
"uri", uri,
"diagnosticCount", len(doc.Errors),
"diagnosticDuration", time.Since(diagStart))
return nil
}
func textDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
start := time.Now()
uri := params.TextDocument.URI
version := params.TextDocument.Version
changeCount := len(params.ContentChanges)
slog.Info("LSP textDocument/didChange received",
"uri", uri,
"version", version,
"changeCount", changeCount)
if debugMode {
slog.Debug("Document change params", "params", params)
}
// Convert GLSP change events to our format
var changes []TextDocumentContentChangeEvent
for i, change := range params.ContentChanges {
// Handle both full and incremental changes by type assertion
changeEvent := TextDocumentContentChangeEvent{}
switch v := change.(type) {
case protocol.TextDocumentContentChangeEvent:
// Handle incremental changes
slog.Debug("Processing incremental change",
"uri", uri,
"changeIndex", i,
"textLength", len(v.Text))
changeEvent.Text = v.Text
if v.Range != nil {
changeEvent.Range = &Range{
Start: Position{
Line: int(v.Range.Start.Line),
Character: int(v.Range.Start.Character),
},
End: Position{
Line: int(v.Range.End.Line),
Character: int(v.Range.End.Character),
},
}
slog.Debug("Change has range",
"uri", uri,
"changeIndex", i,
"startLine", changeEvent.Range.Start.Line,
"startChar", changeEvent.Range.Start.Character,
"endLine", changeEvent.Range.End.Line,
"endChar", changeEvent.Range.End.Character)
}
if v.RangeLength != nil {
rangeLength := int(*v.RangeLength)
changeEvent.RangeLength = &rangeLength
slog.Debug("Change has range length",
"uri", uri,
"changeIndex", i,
"rangeLength", rangeLength)
}
case protocol.TextDocumentContentChangeEventWhole:
// Handle full document changes
slog.Debug("Processing full document change",
"uri", uri,
"changeIndex", i,
"textLength", len(v.Text))
changeEvent.Text = v.Text
case map[string]any:
slog.Warn("Unexpected change event type map[string]any",
"uri", uri,
"changeIndex", i,
"change", v)
// Fallback: try to extract as a map[string]any and get the text
if text, textOk := v["text"].(string); textOk {
changeEvent.Text = text
slog.Debug("Extracted text from map change",
"uri", uri,
"changeIndex", i,
"textLength", len(text))
// Try to extract range if it exists
if rangeData, rangeOk := v["range"]; rangeOk && rangeData != nil {
if rangeMap, rangeMapOk := rangeData.(map[string]any); rangeMapOk {
if startData, startOk := rangeMap["start"].(map[string]any); startOk {
if endData, endOk := rangeMap["end"].(map[string]any); endOk {
if startLine, ok := startData["line"].(float64); ok {
if startChar, ok := startData["character"].(float64); ok {
if endLine, ok := endData["line"].(float64); ok {
if endChar, ok := endData["character"].(float64); ok {
changeEvent.Range = &Range{
Start: Position{
Line: int(startLine),
Character: int(startChar),
},
End: Position{
Line: int(endLine),
Character: int(endChar),
},
}
slog.Debug("Extracted range from map change",
"uri", uri,
"changeIndex", i,
"startLine", int(startLine),
"startChar", int(startChar),
"endLine", int(endLine),
"endChar", int(endChar))
}
}
}
}
}
}
}
}
// Try to extract rangeLength if it exists
if rangeLengthData, rangeLengthOk := v["rangeLength"]; rangeLengthOk && rangeLengthData != nil {
if rangeLengthFloat, ok := rangeLengthData.(float64); ok {
rangeLength := int(rangeLengthFloat)
changeEvent.RangeLength = &rangeLength
slog.Debug("Extracted range length from map change",
"uri", uri,
"changeIndex", i,
"rangeLength", rangeLength)
}
}
}
}
changes = append(changes, changeEvent)
}
updateStart := time.Now()
doc, err := documents.UpdateDocument(uri, version, changes)
if err != nil {
slog.Error("Failed to update document",
"uri", uri,
"version", version,
"error", err,
"duration", time.Since(start))
return fmt.Errorf("failed to update document: %w", err)
}
slog.Info("Document updated successfully",
"uri", uri,
"version", version,
"variableCount", len(doc.Variables),
"titleCount", len(doc.Titles),
"errorCount", len(doc.Errors),
"updateDuration", time.Since(updateStart),
"totalDuration", time.Since(start))
// Send updated diagnostics
diagStart := time.Now()
publishDiagnostics(context, doc)
slog.Debug("Published updated diagnostics",
"uri", uri,
"diagnosticCount", len(doc.Errors),
"diagnosticDuration", time.Since(diagStart))
return nil
}
func textDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
uri := params.TextDocument.URI
slog.Info("LSP textDocument/didClose received", "uri", uri)
if debugMode {
slog.Debug("Document close params", "params", params)
}
documents.CloseDocument(uri)
slog.Info("Document closed", "uri", uri)
return nil
}
func textDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
start := time.Now()
uri := params.TextDocument.URI
line := params.Position.Line
character := params.Position.Character
slog.Info("LSP textDocument/completion received",
"uri", uri,
"line", line,
"character", character)
if debugMode {
slog.Debug("Completion params", "params", params)
}
doc, exists := documents.GetDocument(uri)
if !exists {
slog.Warn("Document not found for completion", "uri", uri)
return []protocol.CompletionItem{}, nil
}
pos := Position{
Line: int(line),
Character: int(character),
}
items := doc.GetCompletionItems(pos)
slog.Info("Completion items generated",
"uri", uri,
"itemCount", len(items),
"duration", time.Since(start))
// Convert our completion items to GLSP format
var glspItems []protocol.CompletionItem
for _, item := range items {
glspItem := protocol.CompletionItem{
Label: item.Label,
Kind: &item.Kind,
Detail: &item.Detail,
}
if item.InsertText != "" {
glspItem.InsertText = &item.InsertText
}
glspItems = append(glspItems, glspItem)
}
if debugMode {
slog.Debug("Completion result", "uri", uri, "items", glspItems)
}
return glspItems, nil
}
func textDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
start := time.Now()
uri := params.TextDocument.URI
line := params.Position.Line
character := params.Position.Character
slog.Info("LSP textDocument/hover received",
"uri", uri,
"line", line,
"character", character)
if debugMode {
slog.Debug("Hover params", "params", params)
}
doc, exists := documents.GetDocument(uri)
if !exists {
slog.Warn("Document not found for hover", "uri", uri)
return nil, nil
}
pos := Position{
Line: int(line),
Character: int(character),
}
variable := doc.GetVariableAt(pos)
if variable != nil {
hoverContent := fmt.Sprintf("**%s** (%s)\n\nAgentFlow variable", variable.Name, variable.Type)
if len(variable.DotPath) > 1 {
hoverContent += fmt.Sprintf("\n\nNested path: %s", joinString(variable.DotPath, " โ "))
}
result := &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: hoverContent,
},
Range: &protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(variable.Range.Start.Line),
Character: protocol.UInteger(variable.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(variable.Range.End.Line),
Character: protocol.UInteger(variable.Range.End.Character),
},
},
}
slog.Info("Hover information provided",
"uri", uri,
"variableName", variable.Name,
"variableType", variable.Type,
"duration", time.Since(start))
if debugMode {
slog.Debug("Hover result", "uri", uri, "result", result)
}
return result, nil
}
operatorInfo := doc.GetOperatorAt(pos)
if operatorInfo != nil {
hoverContent := formatOperatorHover(operatorInfo)
result := &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: hoverContent,
},
}
slog.Info("Operator hover information provided",
"uri", uri,
"operator", operatorInfo.Name,
"duration", time.Since(start))
if debugMode {
slog.Debug("Operator hover result", "uri", uri, "result", result)
}
return result, nil
}
slog.Debug("No hover information found at position",
"uri", uri,
"line", line,
"character", character,
"duration", time.Since(start))
return nil, nil
}
func textDocumentDocumentSymbol(context *glsp.Context, params *protocol.DocumentSymbolParams) (any, error) {
start := time.Now()
uri := params.TextDocument.URI
slog.Info("LSP textDocument/documentSymbol received", "uri", uri)
if debugMode {
slog.Debug("Document symbol params", "params", params)
}
doc, exists := documents.GetDocument(uri)
if !exists {
slog.Warn("Document not found for symbols", "uri", uri)
return []protocol.DocumentSymbol{}, nil
}
var symbols []protocol.DocumentSymbol
// Add titles as symbols
for _, title := range doc.Titles {
symbol := protocol.DocumentSymbol{
Name: title.Name,
Kind: protocol.SymbolKindFunction,
Range: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(title.Range.Start.Line),
Character: protocol.UInteger(title.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(title.Range.End.Line),
Character: protocol.UInteger(title.Range.End.Character),
},
},
SelectionRange: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(title.Range.Start.Line),
Character: protocol.UInteger(title.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(title.Range.End.Line),
Character: protocol.UInteger(title.Range.End.Character),
},
},
}
symbols = append(symbols, symbol)
}
// Add variables as symbols
for _, variable := range doc.Variables {
symbol := protocol.DocumentSymbol{
Name: variable.Name,
Detail: &variable.Type,
Kind: protocol.SymbolKindVariable,
Range: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(variable.Range.Start.Line),
Character: protocol.UInteger(variable.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(variable.Range.End.Line),
Character: protocol.UInteger(variable.Range.End.Character),
},
},
SelectionRange: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(variable.Range.Start.Line),
Character: protocol.UInteger(variable.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(variable.Range.End.Line),
Character: protocol.UInteger(variable.Range.End.Character),
},
},
}
symbols = append(symbols, symbol)
}
slog.Info("Document symbols generated",
"uri", uri,
"titleCount", len(doc.Titles),
"variableCount", len(doc.Variables),
"totalSymbols", len(symbols),
"duration", time.Since(start))
if debugMode {
slog.Debug("Document symbols result", "uri", uri, "symbols", symbols)
}
return symbols, nil
}
func textDocumentSemanticTokensFull(context *glsp.Context, params *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
start := time.Now()
uri := params.TextDocument.URI
slog.Info("LSP textDocument/semanticTokens/full received", "uri", uri)
if debugMode {
slog.Debug("Semantic tokens params", "params", params)
}
doc, exists := documents.GetDocument(uri)
if !exists {
slog.Warn("Document not found for semantic tokens", "uri", uri)
return &protocol.SemanticTokens{Data: []protocol.UInteger{}}, nil
}
tokens := generateGranularSemanticTokens(doc)
result := &protocol.SemanticTokens{
Data: tokens,
}
slog.Info("Semantic tokens generated",
"uri", uri,
"tokenCount", len(tokens)/5, // Each semantic token is 5 values
"duration", time.Since(start))
if debugMode {
slog.Debug("Semantic tokens result", "uri", uri, "tokenData", tokens)
}
return result, nil
}
// publishDiagnostics sends diagnostic information to the client
func publishDiagnostics(context *glsp.Context, doc *Document) {
start := time.Now()
// Initialize with an empty array (never nil) to avoid client errors
diagnostics := []protocol.Diagnostic{}
// Convert parse errors to diagnostics
for _, parseError := range doc.Errors {
severity := protocol.DiagnosticSeverityError
diagnostic := protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(parseError.Range.Start.Line),
Character: protocol.UInteger(parseError.Range.Start.Character),
},
End: protocol.Position{
Line: protocol.UInteger(parseError.Range.End.Line),
Character: protocol.UInteger(parseError.Range.End.Character),
},
},
Severity: &severity, // Use error severity
Source: ptrconv.Str("agentflow"),
Message: parseError.Message,
Code: &protocol.IntegerOrString{Value: parseError.Code},
}
diagnostics = append(diagnostics, diagnostic)
}
publishParams := protocol.PublishDiagnosticsParams{
URI: doc.URI,
// Version: &doc.Version, // optional
Diagnostics: diagnostics,
}
context.Notify(protocol.ServerTextDocumentPublishDiagnostics, publishParams)
slog.Info("Diagnostics published",
"uri", doc.URI,
"diagnosticCount", len(diagnostics),
"duration", time.Since(start))
if debugMode {
slog.Debug("Published diagnostics", "uri", doc.URI, "diagnostics", diagnostics)
}
}
// Helper function since strings.Join isn't available here
func joinString(slice []string, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return slice[0]
}
result := slice[0]
for i := 1; i < len(slice); i++ {
result += sep + slice[i]
}
return result
}
// getClientName extracts client name from client info
func getClientName(clientInfo interface{}) string {
if clientInfo == nil {
return "unknown"
}
if info, ok := clientInfo.(map[string]interface{}); ok {
if name, exists := info["name"]; exists {
if nameStr, ok := name.(string); ok {
return nameStr
}
}
}
return "unknown"
}
package ptrconv
func Str[T ~string](s T) *T { return &s }
func Int[T ~int](i T) *T { return &i }
func Int32[T ~int32](i T) *T { return &i }
func Int64[T ~int64](i T) *T { return &i }
func Uint[T ~uint](i T) *T { return &i }
func Uint32[T ~uint32](i T) *T { return &i }
func Uint64[T ~uint64](i T) *T { return &i }
func Bool[T ~bool](b T) *T { return &b }
func StrNil[T ~string](s T) *T {
if s == "" {
return nil
}
return &s
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package coarse
import (
"bytes"
"fmt"
"github.com/omniaura/agentflow/pkg/token"
"github.com/omniaura/agentflow/pkg/token/kind"
)
// Kind represents coarse-grained token types for AST compatibility
type Kind int
const (
Unset Kind = iota
Title
Var
OptionalBlock
ElseBlock
EndTag
Text
)
// Token represents a coarse-grained token for AST and code generation
type Token struct {
Kind Kind
Start int
End int
}
func (t Token) Get(in []byte) []byte {
return in[t.Start:t.End]
}
func (t Token) Stringify(in []byte) string {
return fmt.Sprintf("%v: [%d:%d] %q", t.Kind, t.Start, t.End, t.Get(in))
}
func (t Token) GetVar(in []byte, typeCache map[string]string) token.VarInfo {
b := in[t.Start:t.End]
// Handle conditional expressions for OptionalBlock tokens
if t.Kind == OptionalBlock {
return token.ParseConditionalExpression(b, typeCache)
}
// Handle regular variable tokens
parts := bytes.Fields(b)
var path [][]byte
var typ string
if len(parts) > 0 {
path = bytes.Split(parts[0], []byte{'.'})
}
// Create cache key from the variable path
pathKey := string(bytes.Join(path, []byte(".")))
if len(parts) > 1 {
// Type is explicitly specified, cache it
typ = string(parts[1])
typeCache[pathKey] = typ
} else {
// No type specified, check cache
if cachedType, exists := typeCache[pathKey]; exists {
typ = cachedType
} else {
typ = "string" // default
}
}
return token.VarInfo{Path: path, Type: typ}
}
// Convert converts fine-grained tokens to coarse-grained tokens for AST compatibility
func Convert(tokens token.Slice, input []byte) []Token {
var coarse []Token
i := 0
for i < len(tokens) {
switch tokens[i].Kind {
case kind.TitleDirective:
// Find the extent of the title text (not including the directive)
var titleStart, titleEnd int
i++ // Skip the directive token
// Skip optional whitespace
if i < len(tokens) && tokens[i].Kind == kind.Whitespace {
i++
}
// Include title text if present
if i < len(tokens) && tokens[i].Kind == kind.TitleText {
titleStart = tokens[i].Start
titleEnd = tokens[i].End
i++
}
// Skip the newline that follows the title directive (don't render it)
if i < len(tokens) && tokens[i].Kind == kind.Whitespace {
// Check if this is a single newline character by looking at token length
if tokens[i].End-tokens[i].Start == 1 {
i++ // Skip this whitespace token (likely a newline)
}
}
// Only add title token if we found title text
if titleEnd > titleStart {
coarse = append(coarse, Token{
Kind: Title,
Start: titleStart,
End: titleEnd,
})
}
case kind.OpenBracket:
// Group bracket sequences into logical units
if i+1 < len(tokens) {
switch tokens[i+1].Kind {
case kind.DirectiveVar:
// Variable: <! ... >
coarse = append(coarse, groupVariableTokens(tokens, i))
i = skipToClosingBracket(tokens, i) + 1
case kind.DirectiveCond:
// Conditional: <? ... >
coarse = append(coarse, groupConditionalTokens(tokens, i))
i = skipToClosingBracket(tokens, i) + 1
case kind.DirectiveEnd:
// End tag: </ ... >
coarse = append(coarse, groupEndTagTokens(tokens, i))
i = skipToClosingBracket(tokens, i) + 1
case kind.DirectiveElse:
// Else: <else>
end := skipToClosingBracket(tokens, i)
coarse = append(coarse, Token{
Kind: ElseBlock,
Start: tokens[i].Start,
End: tokens[end].End,
})
i = end + 1
default:
// Not a recognized directive, treat as text
coarse = append(coarse, Token{
Kind: Text,
Start: tokens[i].Start,
End: tokens[i].End,
})
i++
}
} else {
// Standalone <, treat as text
coarse = append(coarse, Token{
Kind: Text,
Start: tokens[i].Start,
End: tokens[i].End,
})
i++
}
case kind.Text, kind.Whitespace:
// Skip whitespace that precedes a title directive (inter-prompt whitespace)
if tokens[i].Kind == kind.Whitespace && i+1 < len(tokens) && tokens[i+1].Kind == kind.TitleDirective {
i++ // Skip this whitespace token
continue
}
// Group consecutive text/whitespace tokens
start := tokens[i].Start
end := tokens[i].End
i++
for i < len(tokens) && (tokens[i].Kind == kind.Text || tokens[i].Kind == kind.Whitespace) {
// Stop grouping if the next token after whitespace is a title directive
// (this whitespace is inter-prompt separation and should be skipped)
if tokens[i].Kind == kind.Whitespace && i+1 < len(tokens) && tokens[i+1].Kind == kind.TitleDirective {
break
}
end = tokens[i].End
i++
}
// Trim trailing newlines if followed by a title directive
if i < len(tokens) && tokens[i].Kind == kind.TitleDirective {
for end > start && input[end-1] == '\n' {
end--
}
}
coarse = append(coarse, Token{
Kind: Text,
Start: start,
End: end,
})
default:
// Skip unrecognized tokens
i++
}
}
return coarse
}
func groupVariableTokens(tokens token.Slice, start int) Token {
end := skipToClosingBracket(tokens, start)
return Token{
Kind: Var,
Start: tokens[start+2].Start, // Skip < and ! to get to content
End: tokens[end-1].End, // End before >
}
}
func groupConditionalTokens(tokens token.Slice, start int) Token {
end := skipToClosingBracket(tokens, start)
return Token{
Kind: OptionalBlock,
Start: tokens[start+2].Start, // Skip < and ? to get to content
End: tokens[end-1].End, // End before >
}
}
func groupEndTagTokens(tokens token.Slice, start int) Token {
end := skipToClosingBracket(tokens, start)
return Token{
Kind: EndTag,
Start: tokens[start+2].Start, // Skip < and / to get to content
End: tokens[end-1].End, // End before >
}
}
func skipToClosingBracket(tokens token.Slice, start int) int {
for i := start; i < len(tokens); i++ {
if tokens[i].Kind == kind.CloseBracket {
return i
}
}
return len(tokens) - 1
}
package kind
//go:generate go tool stringer -type=Kind
type Kind int
// TODO: add KindDoc, KindVarDoc
// TODO: add .var var predeclare optional; sets the types
// example:
//
// .title say hello to your new friends
// .var names string list join="\n"
// Please say hello to:
// <!names>
//
// INPUT:
// Joe,Mary,Jane
//
// OUTPUT:
// Please say hello to:
// Joe
// Mary
// Jane
const (
Unset Kind = iota
// Basic bracket structure
OpenBracket // "<"
CloseBracket // ">"
// Directive types (what comes after <)
DirectiveVar // "!" in "<!username>"
DirectiveCond // "?" in "<?condition>"
DirectiveEnd // "/" in "</tag>"
DirectiveElse // "else" in "<else>"
// Title directive
TitleDirective // ".title"
TitleText // "System Prompt" in ".title System Prompt"
// Content types (used in variables, conditionals, etc.)
VarName // "username", "user.premium", etc.
TypeName // "int", "bool", "string", "float32", "float64"
Operator // "eq", "gte", "lte", "gt", "lt", "ne"
StringValue // "gold" in "<?tier eq "gold">"
IntValue // "5" in "<?count gte 5>"
BoolValue // "true" in "<?active eq true>"
// Content
Text // Regular text content
Whitespace // Spaces, tabs, newlines (separators)
// Future extension tokens (for later)
// RawBlock // For future raw block support
// Comment // For future comment support
)
func (k Kind) IsTag() bool {
switch k {
case
OpenBracket, CloseBracket,
DirectiveVar, DirectiveCond, DirectiveEnd, DirectiveElse,
VarName, TypeName, Operator, StringValue, IntValue, BoolValue:
return true
}
return false
}
func (k Kind) IsBracket() bool {
switch k {
case OpenBracket, CloseBracket:
return true
}
return false
}
func (k Kind) IsDirective() bool {
switch k {
case DirectiveVar, DirectiveCond, DirectiveEnd, DirectiveElse:
return true
}
return false
}
func (k Kind) IsValue() bool {
switch k {
case VarName, TypeName, StringValue, IntValue, BoolValue:
return true
}
return false
}
func (k Kind) IsContent() bool {
switch k {
case Text:
return true
}
return false
}
func (k Kind) IsStructural() bool {
switch k {
case Whitespace, TitleDirective:
return true
}
return false
}
// Code generated by "stringer -type=Kind"; DO NOT EDIT.
package kind
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Unset-0]
_ = x[OpenBracket-1]
_ = x[CloseBracket-2]
_ = x[DirectiveVar-3]
_ = x[DirectiveCond-4]
_ = x[DirectiveEnd-5]
_ = x[DirectiveElse-6]
_ = x[TitleDirective-7]
_ = x[TitleText-8]
_ = x[VarName-9]
_ = x[TypeName-10]
_ = x[Operator-11]
_ = x[StringValue-12]
_ = x[IntValue-13]
_ = x[BoolValue-14]
_ = x[Text-15]
_ = x[Whitespace-16]
}
const _Kind_name = "UnsetOpenBracketCloseBracketDirectiveVarDirectiveCondDirectiveEndDirectiveElseTitleDirectiveTitleTextVarNameTypeNameOperatorStringValueIntValueBoolValueTextWhitespace"
var _Kind_index = [...]uint8{0, 5, 16, 28, 40, 53, 65, 78, 92, 101, 108, 116, 124, 135, 143, 152, 156, 166}
func (i Kind) String() string {
if i < 0 || i >= Kind(len(_Kind_index)-1) {
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
}
/*
Copyright ยฉ 2024 Omni Aura peyton@omniaura.co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package token
import (
"bytes"
"strconv"
"strings"
"github.com/omniaura/agentflow/pkg/token/kind"
"github.com/peyton-spencer/caseconv/bytcase"
)
type Slice []T
func (t Slice) Equal(o Slice) bool {
if len(t) != len(o) {
return false
}
for i, tok := range t {
if tok != o[i] {
return false
}
}
return true
}
type T struct {
Kind kind.Kind
Start int
End int
}
func (t T) Get(in []byte) []byte {
return in[t.Start:t.End]
}
func (t T) GetWrap(in []byte, left, right byte) []byte {
out := make([]byte, 0, len(in)+2)
out = append(out, left)
out = append(out, in[t.Start:t.End]...)
out = append(out, right)
return out
}
func (t T) GetWrapLL(in []byte, left []byte, right byte) []byte {
out := make([]byte, 0, len(in)+len(left)+1)
out = append(out, left...)
out = append(out, in[t.Start:t.End]...)
out = append(out, right)
return out
}
func (t T) GetJSFmtVar(in []byte) []byte {
out := make([]byte, 0, len(in)+3)
out = append(out, '$', '{')
out = append(out, bytcase.ToLowerCamel(in[t.Start:t.End])...)
out = append(out, '}')
return out
}
var (
cmdTitle = []byte(".title")
)
func Tokenize(input []byte) (Slice, error) {
var tokens []T
i := 0
for i < len(input) {
// Check for .title directive at start of line
if i == 0 || (i > 0 && input[i-1] == '\n') {
if i < len(input) && input[i] == '.' {
if title := tryParseTitle(input, i); title != nil {
tokens = append(tokens, title...)
i += titleLength(input, i)
continue
}
}
}
// Check for < with lookahead for valid directives
if input[i] == '<' {
if tag := tryParseTag(input, i); tag != nil {
tokens = append(tokens, tag...)
i += tagLength(input, i)
continue
}
}
// Regular text content (including whitespace when not significant)
text := parseText(input, i)
tokens = append(tokens, text)
i += text.End - text.Start
}
return tokens, nil
}
// tryParseTitle attempts to parse .title directive
func tryParseTitle(input []byte, start int) []T {
if start+6 >= len(input) || !bytes.HasPrefix(input[start:], []byte(".title")) {
return nil
}
// Check that ".title" is followed by whitespace or end of input
if start+6 < len(input) && !isWhitespace(input[start+6]) {
return nil
}
var tokens []T
// .title directive
tokens = append(tokens, T{
Kind: kind.TitleDirective,
Start: start,
End: start + 6,
})
pos := start + 6
// Optional whitespace after .title
if pos < len(input) && isWhitespace(input[pos]) {
wsStart := pos
for pos < len(input) && isWhitespace(input[pos]) && input[pos] != '\n' {
pos++
}
tokens = append(tokens, T{
Kind: kind.Whitespace,
Start: wsStart,
End: pos,
})
}
// Title text (rest of line)
if pos < len(input) && input[pos] != '\n' {
textStart := pos
for pos < len(input) && input[pos] != '\n' {
pos++
}
// Trim trailing whitespace from title text
textEnd := pos
for textEnd > textStart && isWhitespace(input[textEnd-1]) {
textEnd--
}
if textEnd > textStart {
tokens = append(tokens, T{
Kind: kind.TitleText,
Start: textStart,
End: textEnd,
})
}
}
// Include the newline after title as whitespace if present
if pos < len(input) && input[pos] == '\n' {
tokens = append(tokens, T{
Kind: kind.Whitespace,
Start: pos,
End: pos + 1,
})
}
return tokens
}
// tryParseTag attempts to parse < with lookahead for valid directives
func tryParseTag(input []byte, start int) []T {
if start >= len(input) || input[start] != '<' {
return nil
}
// Check what follows <
if start+1 >= len(input) {
return nil
}
switch input[start+1] {
case '!':
return parseVarTag(input, start)
case '?':
return parseCondTag(input, start)
case '/':
return parseEndTag(input, start)
default:
// Check for <else>
if start+4 < len(input) && bytes.HasPrefix(input[start:], []byte("<else")) {
if start+5 >= len(input) || input[start+5] == '>' {
return parseElseTag(input, start)
}
}
return nil
}
}
// parseVarTag parses <!variable> or <!variable type>
func parseVarTag(input []byte, start int) []T {
var tokens []T
pos := start
// Find closing >
closePos := -1
for i := start + 2; i < len(input); i++ {
if input[i] == '>' {
closePos = i
break
}
}
if closePos == -1 {
return nil
}
// OpenBracket + DirectiveVar
tokens = append(tokens, T{Kind: kind.OpenBracket, Start: pos, End: pos + 1})
pos++
tokens = append(tokens, T{Kind: kind.DirectiveVar, Start: pos, End: pos + 1})
pos++
// Parse content between <! and >
content := input[pos:closePos]
contentTokens := parseVarContent(content, pos)
tokens = append(tokens, contentTokens...)
// CloseBracket
tokens = append(tokens, T{Kind: kind.CloseBracket, Start: closePos, End: closePos + 1})
return tokens
}
// parseCondTag parses <?variable> or <?variable operator value>
func parseCondTag(input []byte, start int) []T {
var tokens []T
pos := start
// Find closing >
closePos := -1
for i := start + 2; i < len(input); i++ {
if input[i] == '>' {
closePos = i
break
}
}
if closePos == -1 {
return nil
}
// OpenBracket + DirectiveCond
tokens = append(tokens, T{Kind: kind.OpenBracket, Start: pos, End: pos + 1})
pos++
tokens = append(tokens, T{Kind: kind.DirectiveCond, Start: pos, End: pos + 1})
pos++
// Parse content between <? and >
content := input[pos:closePos]
contentTokens := parseCondContent(content, pos)
tokens = append(tokens, contentTokens...)
// CloseBracket
tokens = append(tokens, T{Kind: kind.CloseBracket, Start: closePos, End: closePos + 1})
return tokens
}
// parseEndTag parses </variable>
func parseEndTag(input []byte, start int) []T {
var tokens []T
pos := start
// Find closing >
closePos := -1
for i := start + 2; i < len(input); i++ {
if input[i] == '>' {
closePos = i
break
}
}
if closePos == -1 {
return nil
}
// OpenBracket + DirectiveEnd
tokens = append(tokens, T{Kind: kind.OpenBracket, Start: pos, End: pos + 1})
pos++
tokens = append(tokens, T{Kind: kind.DirectiveEnd, Start: pos, End: pos + 1})
pos++
// Variable name
if closePos > pos {
tokens = append(tokens, T{Kind: kind.VarName, Start: pos, End: closePos})
}
// CloseBracket
tokens = append(tokens, T{Kind: kind.CloseBracket, Start: closePos, End: closePos + 1})
return tokens
}
// parseElseTag parses <else>
func parseElseTag(input []byte, start int) []T {
var tokens []T
// OpenBracket
tokens = append(tokens, T{Kind: kind.OpenBracket, Start: start, End: start + 1})
// DirectiveElse
tokens = append(tokens, T{Kind: kind.DirectiveElse, Start: start + 1, End: start + 5})
// CloseBracket
tokens = append(tokens, T{Kind: kind.CloseBracket, Start: start + 5, End: start + 6})
return tokens
}
// parseVarContent parses the content inside <!...>
func parseVarContent(content []byte, offset int) []T {
var tokens []T
pos := 0
// Skip leading whitespace
for pos < len(content) && isWhitespace(content[pos]) {
pos++
}
if pos >= len(content) {
return tokens
}
// Variable name (everything until whitespace or end)
varStart := pos
for pos < len(content) && !isWhitespace(content[pos]) {
pos++
}
if pos > varStart {
tokens = append(tokens, T{
Kind: kind.VarName,
Start: offset + varStart,
End: offset + pos,
})
}
// Optional whitespace + type
if pos < len(content) {
// Whitespace
wsStart := pos
for pos < len(content) && isWhitespace(content[pos]) {
pos++
}
if pos > wsStart {
tokens = append(tokens, T{
Kind: kind.Whitespace,
Start: offset + wsStart,
End: offset + pos,
})
}
// Type name
if pos < len(content) {
typeStart := pos
for pos < len(content) && !isWhitespace(content[pos]) {
pos++
}
if pos > typeStart {
tokens = append(tokens, T{
Kind: kind.TypeName,
Start: offset + typeStart,
End: offset + pos,
})
}
}
}
return tokens
}
// parseCondContent parses the content inside <?...>
func parseCondContent(content []byte, offset int) []T {
var tokens []T
// Split on whitespace and parse each part
parts := bytes.Fields(content)
pos := 0
for i, part := range parts {
// Skip to the start of this part
for pos < len(content) && isWhitespace(content[pos]) {
if i > 0 {
// Add whitespace token
wsStart := pos
for pos < len(content) && isWhitespace(content[pos]) {
pos++
}
tokens = append(tokens, T{
Kind: kind.Whitespace,
Start: offset + wsStart,
End: offset + pos,
})
break
}
pos++
}
partStart := pos
partEnd := pos + len(part)
if i == 0 {
// First part is variable name
tokens = append(tokens, T{
Kind: kind.VarName,
Start: offset + partStart,
End: offset + partEnd,
})
} else if isOperator(string(part)) {
// Operator
tokens = append(tokens, T{
Kind: kind.Operator,
Start: offset + partStart,
End: offset + partEnd,
})
} else if isType(string(part)) {
// Type
tokens = append(tokens, T{
Kind: kind.TypeName,
Start: offset + partStart,
End: offset + partEnd,
})
} else if isBoolValue(string(part)) {
// Boolean value
tokens = append(tokens, T{
Kind: kind.BoolValue,
Start: offset + partStart,
End: offset + partEnd,
})
} else if isIntValue(string(part)) {
// Integer value
tokens = append(tokens, T{
Kind: kind.IntValue,
Start: offset + partStart,
End: offset + partEnd,
})
} else {
// String value (including quoted strings)
tokens = append(tokens, T{
Kind: kind.StringValue,
Start: offset + partStart,
End: offset + partEnd,
})
}
pos = partEnd
}
return tokens
}
// parseWhitespace parses whitespace characters
func parseWhitespace(input []byte, start int) T {
pos := start
for pos < len(input) && isWhitespace(input[pos]) {
pos++
}
return T{
Kind: kind.Whitespace,
Start: start,
End: pos,
}
}
// parseText parses regular text content
func parseText(input []byte, start int) T {
pos := start
for pos < len(input) {
if input[pos] == '<' {
// Check if this might be a tag
if tryParseTag(input, pos) != nil {
break
}
}
if input[pos] == '.' && (pos == 0 || input[pos-1] == '\n') {
// Check if this might be a title directive
if tryParseTitle(input, pos) != nil {
break
}
}
// For text parsing, continue through whitespace unless we hit a special token
// This allows "hello world" to be parsed as a single text token
pos++
}
return T{
Kind: kind.Text,
Start: start,
End: pos,
}
}
// Helper functions
func titleLength(input []byte, start int) int {
pos := start + 6 // ".title"
for pos < len(input) && input[pos] != '\n' {
pos++
}
// Include the newline if present
if pos < len(input) && input[pos] == '\n' {
pos++
}
return pos - start
}
func tagLength(input []byte, start int) int {
for i := start + 1; i < len(input); i++ {
if input[i] == '>' {
return i - start + 1
}
}
return len(input) - start
}
func isWhitespace(b byte) bool {
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}
func isOperator(s string) bool {
operators := []string{"eq", "ne", "gt", "lt", "gte", "lte"}
for _, op := range operators {
if s == op {
return true
}
}
return false
}
func isType(s string) bool {
types := []string{"int", "bool", "string", "float", "float32", "float64"}
for _, t := range types {
if s == t {
return true
}
}
return false
}
func isBoolValue(s string) bool {
return s == "true" || s == "false"
}
func isIntValue(s string) bool {
_, err := strconv.Atoi(s)
return err == nil
}
func (t T) Stringify(in []byte) string {
var buf strings.Builder
buf.Grow(len(in) + 100)
buf.WriteString(t.Kind.String())
buf.WriteString(":\t[")
buf.WriteString(strconv.Itoa(t.Start))
buf.WriteString(":")
buf.WriteString(strconv.Itoa(t.End))
buf.WriteString("]\t")
if t.Start < 0 || t.End > len(in) || t.Start > t.End {
buf.WriteString("INVALID BOUNDS")
} else {
buf.WriteString("\"")
buf.Write(in[t.Start:t.End])
buf.WriteString("\"")
}
return buf.String()
}
func (s Slice) Stringify(in []byte) string {
if len(in) == 0 {
return "no content"
}
if len(s) == 0 {
return "no tokens"
}
var buf strings.Builder
buf.Grow(len(in) + 100)
for i, tok := range s {
buf.WriteString(tok.Stringify(in))
if i != len(s)-1 {
buf.WriteRune('\n')
}
}
return buf.String()
}
// VarInfo holds the parsed variable path and type from a var token
// Path is the dot-separated path as a slice of []byte (e.g. ["user", "subscription", "tier"])
// Type is the type string (e.g. "string", "int", "bool")
// Operator is the conditional operator (e.g. ">=", "==", "!=") for OptionalBlock tokens
// Operand is the value to compare against (e.g. "30", "true", "\"gold\"")
type VarInfo struct {
Path [][]byte
Type string
Operator string // For conditionals: ">=", "<=", "==", "!=", ">", "<"
Operand string // For conditionals: the value to compare against
}
// GetVar parses the variable name and type from a var token
// The typeCache map stores previously seen variable types for reuse
// For OptionalBlock tokens, it also parses conditional operators and operands
func (t T) GetVar(in []byte, typeCache map[string]string) VarInfo {
b := in[t.Start:t.End]
// Handle conditional expressions for conditional directive tokens
if t.Kind == kind.DirectiveCond {
return ParseConditionalExpression(b, typeCache)
}
// Handle regular variable tokens
parts := bytes.Fields(b)
var path [][]byte
var typ string
if len(parts) > 0 {
path = bytes.Split(parts[0], []byte{'.'})
}
// Create cache key from the variable path
pathKey := string(bytes.Join(path, []byte(".")))
if len(parts) > 1 {
// Type is explicitly specified, cache it
typ = string(parts[1])
typeCache[pathKey] = typ
} else {
// No type specified, check cache
if cachedType, exists := typeCache[pathKey]; exists {
typ = cachedType
} else {
typ = "string" // default
}
}
return VarInfo{Path: path, Type: typ}
}
// ParseConditionalExpression parses conditional expressions like "writer.current_streak gte 30"
func ParseConditionalExpression(expr []byte, typeCache map[string]string) VarInfo {
exprStr := string(expr)
// List of word operators to check for
operators := []string{"gte", "lte", "gt", "lt", "eq", "ne"}
for _, op := range operators {
if idx := strings.Index(exprStr, " "+op+" "); idx != -1 {
// Found an operator
varPart := strings.TrimSpace(exprStr[:idx])
operandPart := strings.TrimSpace(exprStr[idx+len(op)+2:]) // +2 for the spaces around operator
// Parse variable path and type
varInfo := parseVariablePart(varPart, typeCache)
// Check if operand is a valid variable reference (dot notation with valid identifiers)
if isValidVariableRef(operandPart) {
// Operand is another variable - convert to Go field access
operandPath := strings.Split(operandPart, ".")
var operandFieldAccess strings.Builder
operandFieldAccess.WriteString("input.")
for i, part := range operandPath {
// Convert to CamelCase using the proper bytcase function
if len(part) > 0 {
operandFieldAccess.Write(bytcase.ToCamel([]byte(part)))
if i < len(operandPath)-1 {
operandFieldAccess.WriteString(".")
}
}
}
varInfo.Operand = operandFieldAccess.String()
} else {
// Infer type from operand constant
inferredType := inferTypeFromOperand(operandPart)
if inferredType != "string" || varInfo.Type == "string" {
varInfo.Type = inferredType
// Cache the inferred type
pathKey := string(bytes.Join(varInfo.Path, []byte(".")))
typeCache[pathKey] = inferredType
}
varInfo.Operand = formatOperandForGeneration(operandPart, varInfo.Type)
}
varInfo.Operator = op
return varInfo
}
}
// No operator found - treat as simple truthiness check
varInfo := parseVariablePart(exprStr, typeCache)
return varInfo
}
// formatOperandForGeneration formats and sanitizes operands for safe code generation.
// It validates that operands contain only expected values for their type to prevent
// code injection through crafted .af files.
func formatOperandForGeneration(operand, varType string) string {
operand = strings.TrimSpace(operand)
switch varType {
case "string":
// Strip surrounding quotes if present, then safely re-quote using strconv.Quote
// which properly escapes all special characters, preventing code injection.
inner := stripQuotes(operand)
return strconv.Quote(inner)
case "int":
// Validate that the operand is a valid integer literal
if _, err := strconv.Atoi(operand); err == nil {
return operand
}
// Invalid integer operand - return zero value to prevent injection
return "0"
case "float32", "float64":
// Validate that the operand is a valid float literal
if _, err := strconv.ParseFloat(operand, 64); err == nil {
return operand
}
// Invalid float operand - return zero value to prevent injection
return "0"
case "bool":
// Only allow exact boolean literals
if operand == "true" || operand == "false" {
return operand
}
// Invalid boolean operand - return false to prevent injection
return "false"
default:
// Default to safely quoted string
inner := stripQuotes(operand)
return strconv.Quote(inner)
}
}
// stripQuotes removes surrounding double or single quotes from a string.
func stripQuotes(s string) string {
if len(s) >= 2 {
if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
return s[1 : len(s)-1]
}
}
return s
}
// parseVariablePart parses the variable name and optional explicit type
func parseVariablePart(varPart string, typeCache map[string]string) VarInfo {
parts := strings.Fields(varPart)
var path [][]byte
var typ string
if len(parts) > 0 {
path = bytes.Split([]byte(parts[0]), []byte{'.'})
}
// Create cache key from the variable path
pathKey := string(bytes.Join(path, []byte(".")))
if len(parts) > 1 {
// Type is explicitly specified, cache it
typ = parts[1]
typeCache[pathKey] = typ
} else {
// No type specified, check cache
if cachedType, exists := typeCache[pathKey]; exists {
typ = cachedType
} else {
typ = "string" // default
}
}
return VarInfo{Path: path, Type: typ}
}
// inferTypeFromOperand infers the Go type from the operand value
func inferTypeFromOperand(operand string) string {
operand = strings.TrimSpace(operand)
// Check for boolean values
if operand == "true" || operand == "false" {
return "bool"
}
// Check for quoted strings
if (strings.HasPrefix(operand, "\"") && strings.HasSuffix(operand, "\"")) ||
(strings.HasPrefix(operand, "'") && strings.HasSuffix(operand, "'")) {
return "string"
}
// Check for floating point numbers
if strings.Contains(operand, ".") {
if _, err := strconv.ParseFloat(operand, 64); err == nil {
return "float64"
}
}
// Check for integers
if _, err := strconv.Atoi(operand); err == nil {
return "int"
}
// Default to string for unquoted values
return "string"
}
// isNumericLiteral checks if a string represents a numeric literal (int or float)
func isNumericLiteral(operand string) bool {
operand = strings.TrimSpace(operand)
// Check for floating point numbers
if strings.Contains(operand, ".") {
if _, err := strconv.ParseFloat(operand, 64); err == nil {
return true
}
}
// Check for integers
if _, err := strconv.Atoi(operand); err == nil {
return true
}
return false
}
// hasComparisonOperator checks if the token content contains any comparison operators
func hasComparisonOperator(content string) bool {
operators := []string{">=", "<=", "==", "!=", ">", "<"}
for _, op := range operators {
if strings.Contains(content, op) {
return true
}
}
return false
}
// isValidVariableRef checks if a string is a valid dot-separated variable reference
// where each segment is a valid identifier (e.g., "config.max_score").
// It must contain at least one dot and not be a numeric literal, quoted string, or empty.
func isValidVariableRef(s string) bool {
if !strings.Contains(s, ".") {
return false
}
if strings.Contains(s, "\"") || strings.Contains(s, "'") {
return false
}
if isNumericLiteral(s) {
return false
}
parts := strings.Split(s, ".")
for _, part := range parts {
if !isValidIdentifier(part) {
return false
}
}
return true
}
// isValidIdentifier checks if a string is a valid Go-style identifier
// (letters, digits, and underscores, not starting with a digit).
func isValidIdentifier(s string) bool {
if len(s) == 0 {
return false
}
for i, r := range s {
if r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
continue
}
if i > 0 && r >= '0' && r <= '9' {
continue
}
return false
}
return true
}