Skip to content

Commit 7b0cbd7

Browse files
committed
app/summarize: add free tier key
Add a free tier LLM key to test gradient-engineer without providing any keys.
1 parent 27eaa8f commit 7b0cbd7

File tree

3 files changed

+55
-8
lines changed

3 files changed

+55
-8
lines changed

.github/workflows/build-app.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ jobs:
3333
- name: Checkout
3434
uses: actions/checkout@v5
3535

36+
- name: Recreate .fk1.txt and .fk2.txt
37+
shell: bash
38+
env:
39+
FK1_TXT_B64: ${{ secrets.FK1_TXT_B64 }}
40+
FK2_TXT_B64: ${{ secrets.FK2_TXT_B64 }}
41+
run: |
42+
set -euo pipefail
43+
mkdir -p app
44+
if [ -n "${FK1_TXT_B64:-}" ]; then printf "%s" "$FK1_TXT_B64" | openssl base64 -d -A > app/.fk1.txt; fi
45+
if [ -n "${FK2_TXT_B64:-}" ]; then printf "%s" "$FK2_TXT_B64" | openssl base64 -d -A > app/.fk2.txt; fi
46+
3647
- name: Set up Go
3748
uses: actions/setup-go@v5
3849
with:

app/.fk0.txt

Whitespace-only changes.

app/summarize.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"embed"
56
"fmt"
67
"os"
78
"strings"
@@ -28,6 +29,7 @@ type Summarizer struct {
2829
openaiClient openai.Client
2930
anthropicClient anthropic.Client
3031
model string
32+
models []string // fallback models
3133
disabled bool
3234
}
3335

@@ -37,10 +39,12 @@ type Summarizer struct {
3739
// - Else if OPENROUTER_API_KEY is set, use OpenRouter base and that key
3840
// - Else if OPENAI_API_KEY starts with "sk-or-v1-", treat it as an OpenRouter key
3941
// - Else if OPENAI_API_KEY is set, use default OpenAI base and that key
42+
// - Else fallback to fk
4043
// Base URL can be overridden via OPENAI_BASE_URL for OpenAI/OpenRouter.
4144
func NewSummarizer() *Summarizer {
4245
baseOverride := os.Getenv("OPENAI_BASE_URL")
4346
openRouterKey := os.Getenv("OPENROUTER_API_KEY")
47+
fk := getFK()
4448
openAIKey := os.Getenv("OPENAI_API_KEY")
4549
anthropicKey := os.Getenv("ANTHROPIC_API_KEY")
4650

@@ -50,7 +54,7 @@ func NewSummarizer() *Summarizer {
5054
}
5155

5256
// If no key is provided for any provider, mark summarizer as disabled.
53-
if strings.TrimSpace(anthropicKey) == "" && strings.TrimSpace(openRouterKey) == "" && strings.TrimSpace(openAIKey) == "" {
57+
if strings.TrimSpace(anthropicKey) == "" && strings.TrimSpace(openRouterKey) == "" && strings.TrimSpace(openAIKey) == "" && strings.TrimSpace(fk) == "" {
5458
return &Summarizer{
5559
provider: "none",
5660
model: "",
@@ -70,12 +74,13 @@ func NewSummarizer() *Summarizer {
7074
}
7175

7276
usingOpenRouter := openRouterKey != ""
77+
usingFK := openRouterKey == "" && openAIKey == ""
7378

7479
// Determine base URL for OpenAI/OpenRouter
7580
baseURL := ""
7681
if baseOverride != "" {
7782
baseURL = baseOverride
78-
} else if usingOpenRouter {
83+
} else if usingOpenRouter || usingFK {
7984
baseURL = "https://openrouter.ai/api/v1"
8085
}
8186

@@ -84,8 +89,12 @@ func NewSummarizer() *Summarizer {
8489
if baseURL != "" {
8590
opts = append(opts, openaiopt.WithBaseURL(baseURL))
8691
}
87-
if usingOpenRouter {
88-
opts = append(opts, openaiopt.WithAPIKey(openRouterKey))
92+
if usingOpenRouter || usingFK {
93+
if usingOpenRouter {
94+
opts = append(opts, openaiopt.WithAPIKey(openRouterKey))
95+
} else {
96+
opts = append(opts, openaiopt.WithAPIKey(fk))
97+
}
8998
// OpenRouter attribution headers
9099
opts = append(opts,
91100
openaiopt.WithHeader("X-Title", "gradient-engineer"),
@@ -95,18 +104,23 @@ func NewSummarizer() *Summarizer {
95104
opts = append(opts, openaiopt.WithAPIKey(openAIKey))
96105
}
97106

98-
cli := openai.NewClient(opts...)
99-
100107
// Choose a model slug compatible with provider
101108
model := "gpt-4.1"
109+
models := []string{}
102110
if usingOpenRouter {
103111
model = "openai/gpt-4.1"
112+
} else if usingFK {
113+
model = "deepseek/deepseek-chat-v3.1:free"
114+
models = []string{"deepseek/deepseek-chat-v3-0324:free", "moonshotai/kimi-k2:free", "meta-llama/llama-3.3-70b-instruct:free"}
104115
}
105116

117+
cli := openai.NewClient(opts...)
118+
106119
return &Summarizer{
107120
provider: "openai",
108121
openaiClient: cli,
109122
model: model,
123+
models: models,
110124
disabled: false,
111125
}
112126
}
@@ -159,13 +173,19 @@ func (s *Summarizer) Summarize(systemPrompt string, commands []SummaryCommand) (
159173
}
160174

161175
// OpenAI/OpenRouter path
162-
resp, err := s.openaiClient.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
176+
params := openai.ChatCompletionNewParams{
163177
Model: s.model,
164178
Messages: []openai.ChatCompletionMessageParamUnion{
165179
openai.SystemMessage(systemPrompt),
166180
openai.UserMessage(userContent),
167181
},
168-
})
182+
}
183+
if len(s.models) > 0 {
184+
params.SetExtraFields(map[string]interface{}{
185+
"models": s.models,
186+
})
187+
}
188+
resp, err := s.openaiClient.Chat.Completions.New(ctx, params)
169189
if err != nil {
170190
return "", err
171191
}
@@ -186,3 +206,19 @@ func summarizeCmd(s *Summarizer, systemPrompt string, commands []SummaryCommand)
186206
return llmMsg{summary: summary}
187207
}
188208
}
209+
210+
//go:embed .fk*.txt
211+
var fk embed.FS
212+
213+
func getFK() string {
214+
fk1, err1 := fk.ReadFile(".fk1.txt")
215+
fk2, err2 := fk.ReadFile(".fk2.txt")
216+
if err1 != nil || err2 != nil {
217+
return ""
218+
}
219+
fk3 := make([]byte, len(fk1))
220+
for i := 0; i < len(fk1); i++ {
221+
fk3[i] = byte((int(fk1[i]^fk2[i]) + 256 - i - 42) ^ 0xFF)
222+
}
223+
return string(fk3)
224+
}

0 commit comments

Comments
 (0)