Template
Closes #1. Closes #2. A repo generated from this template failed `task check` on errcheck from its first commit, while the template's own `task check` was green. The reason is not in the code: Go ignores directories whose names begin with "_" or "." when expanding package patterns like ./... The entrypoint lives in cmd/__PROJECT_NAME__. So `go build ./...`, `go vet ./...`, `go test ./...` and `golangci-lint run ./...` ALL silently skip the one package every generated repo is built around: $ go list ./... __MODULE_PATH__/internal/agent __MODULE_PATH__/pkg/litellm <- no cmd package After substitution the directory has a real name, becomes visible, and the defect appears. Template green, children red, by construction -- and invisible to anyone reading either repo's CI. Fixes the one live defect: `defer shutdown(ctx)` dropped an error, and SetupTelemetry returns func(context.Context) error. Two of the three defects these issues describe were already fixed and neither issue knew: go.sum has been committed for some time, and resp.Body.Close is already wrapped. Verified before touching anything rather than trusted from the issue text. Adds scripts/tests/generated-child_test.sh, wired into `task check`: it substitutes the tracked tree into a throwaway child and gates go list / build / vet / golangci-lint on the RESULT. Without it the next entrypoint defect rots exactly the same way. Not done by adding ./cmd/__PROJECT_NAME__ to the lint command, which does work -- naming a path directly bypasses the pattern exclusion -- because it reports a gofumpt violation on the import block that does not exist in any child. The formatter's import grouping depends on the module path and the template's is a placeholder, so that route turns the gate red over an artifact of being a template. The harness reads tracked files from the working tree rather than `git archive HEAD`: it runs inside `task check`, so it must gate what is about to be committed. Archiving HEAD reported a stale pass against an unstaged fix on first use here. Proven to fail when it should: red on the unfixed entrypoint, and red again on the stale-HEAD read, before going green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"google.golang.org/adk/agent"
|
|
"google.golang.org/adk/agent/llmagent"
|
|
"google.golang.org/adk/runner"
|
|
"google.golang.org/adk/session"
|
|
"google.golang.org/genai"
|
|
|
|
"__MODULE_PATH__/pkg/litellm"
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
shutdown, err := litellm.SetupTelemetry(ctx)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "telemetry: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
// Wrapped rather than bare: errcheck flags a deferred call that drops an
|
|
// error, and SetupTelemetry returns func(context.Context) error. The bare
|
|
// form shipped for months because this package lives in a directory whose
|
|
// name starts with an underscore, which Go's ./... expansion skips -- so
|
|
// the template's own lint never saw it while every generated repo, where
|
|
// the directory has a real name, inherited the failure.
|
|
defer func() { _ = shutdown(ctx) }()
|
|
|
|
llm := litellm.New(
|
|
env("LITELLM_MODEL", "berget/llama-3.3-70b"),
|
|
env("LITELLM_BASE_URL", "https://llm-api.d-ma.be/v1"),
|
|
mustEnv("LITELLM_API_KEY"),
|
|
)
|
|
|
|
ag, err := llmagent.New(llmagent.Config{
|
|
Name: "__PROJECT_NAME__",
|
|
Description: "TODO: describe what this agent does",
|
|
Model: llm,
|
|
Instruction: "You are a helpful assistant.",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "agent: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
r, err := runner.New(runner.Config{
|
|
AppName: "__PROJECT_NAME__",
|
|
Agent: ag,
|
|
SessionService: session.InMemoryService(),
|
|
AutoCreateSession: true,
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "runner: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
msg := genai.NewContentFromText("Hello!", "user")
|
|
events := r.Run(ctx, "user-1", "session-1", msg, agent.RunConfig{})
|
|
|
|
for ev, err := range events {
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "run: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if ev == nil || ev.Content == nil {
|
|
continue
|
|
}
|
|
for _, p := range ev.Content.Parts {
|
|
if p != nil && p.Text != "" {
|
|
fmt.Println(p.Text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func env(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func mustEnv(key string) string {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
fmt.Fprintf(os.Stderr, "required env var %s not set\n", key)
|
|
os.Exit(1)
|
|
}
|
|
return v
|
|
}
|