some opts
This commit is contained in:
@@ -90,7 +90,6 @@ var defCfg = Cfg{"https://api.kilo.ai/api/openrouter", "openrouter/free", "-", 0
|
|||||||
|
|
||||||
const opencodeAgentVersion = "opencode/1.18.31"
|
const opencodeAgentVersion = "opencode/1.18.31"
|
||||||
const opencodeProviderUA = "ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14"
|
const opencodeProviderUA = "ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14"
|
||||||
const opencodeIDAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
||||||
const opencodeProjectID = "global"
|
const opencodeProjectID = "global"
|
||||||
|
|
||||||
// opencodeTailAlphabet is the character set used for the 14-character random
|
// opencodeTailAlphabet is the character set used for the 14-character random
|
||||||
@@ -110,54 +109,67 @@ func atoiD(s string, d int) int {
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryModelsContextWindow(cfg *Cfg) int {
|
func toInt(v any, def int) int {
|
||||||
|
switch n := v.(type) {
|
||||||
|
case int:
|
||||||
|
return n
|
||||||
|
case float64:
|
||||||
|
return int(n)
|
||||||
|
case string:
|
||||||
|
return atoiD(n, def)
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchModels(cfg *Cfg, timeoutSec int) ([]map[string]any, error) {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: proxyFromEnv,
|
Proxy: proxyFromEnv,
|
||||||
DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
|
DialContext: (&net.Dialer{Timeout: time.Duration(timeoutSec) * time.Second}).DialContext,
|
||||||
},
|
},
|
||||||
Timeout: 3 * time.Second,
|
Timeout: time.Duration(timeoutSec) * time.Second,
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return nil, err
|
||||||
}
|
}
|
||||||
applyLLMHeaders(req, cfg, ocRequestID())
|
applyLLMHeaders(req, cfg, ocRequestID())
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil || resp.StatusCode >= 400 {
|
if err != nil {
|
||||||
return 0
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return nil, fmt.Errorf("HTTP %d from /models", resp.StatusCode)
|
||||||
|
}
|
||||||
var res struct {
|
var res struct {
|
||||||
Data []map[string]any `json:"data"`
|
Data []map[string]any `json:"data"`
|
||||||
Models []map[string]any `json:"models"`
|
Models []map[string]any `json:"models"`
|
||||||
}
|
}
|
||||||
if json.NewDecoder(resp.Body).Decode(&res) != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
return 0
|
return nil, err
|
||||||
}
|
}
|
||||||
list := res.Data
|
if len(res.Data) > 0 {
|
||||||
if len(list) == 0 {
|
return res.Data, nil
|
||||||
list = res.Models
|
}
|
||||||
|
return res.Models, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryModelsContextWindow(cfg *Cfg) int {
|
||||||
|
list, err := fetchModels(cfg, 3)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
id, _ := item["id"].(string)
|
id, _ := item["id"].(string)
|
||||||
if id == cfg.Model || strings.EqualFold(id, cfg.Model) {
|
if id == cfg.Model || strings.EqualFold(id, cfg.Model) {
|
||||||
for _, key := range []string{"context_window", "context_length", "max_context_length", "max_model_len", "context_size", "max_tokens", "max_input_tokens"} {
|
for _, key := range []string{"context_window", "context_length", "max_context_length", "max_model_len", "context_size", "max_tokens", "max_input_tokens"} {
|
||||||
if val, ok := item[key]; ok {
|
if n := toInt(item[key], 0); n > 0 {
|
||||||
switch v := val.(type) {
|
|
||||||
case float64:
|
|
||||||
if v > 0 {
|
|
||||||
return int(v)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
if n := atoiD(v, 0); n > 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,37 +208,10 @@ func listModels(cfg *Cfg) (string, error) {
|
|||||||
if t < 10 {
|
if t < 10 {
|
||||||
t = 10
|
t = 10
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
list, err := fetchModels(cfg, t)
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: proxyFromEnv,
|
|
||||||
DialContext: (&net.Dialer{Timeout: time.Duration(t) * time.Second}).DialContext,
|
|
||||||
},
|
|
||||||
Timeout: time.Duration(t) * time.Second,
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", strings.TrimRight(cfg.Endpoint, "/")+"/models", nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
applyLLMHeaders(req, cfg, ocRequestID())
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode >= 400 {
|
|
||||||
return "", fmt.Errorf("HTTP %d from /models", resp.StatusCode)
|
|
||||||
}
|
|
||||||
var res struct {
|
|
||||||
Data []map[string]any `json:"data"`
|
|
||||||
Models []map[string]any `json:"models"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse /models response: %v", err)
|
|
||||||
}
|
|
||||||
list := res.Data
|
|
||||||
if len(list) == 0 {
|
|
||||||
list = res.Models
|
|
||||||
}
|
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return "No models returned by the endpoint.", nil
|
return "No models returned by the endpoint.", nil
|
||||||
}
|
}
|
||||||
@@ -286,14 +271,20 @@ func parseCfgFile(path string, cfg *Cfg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyEnvCfg(cfg *Cfg) {
|
func applyEnvCfg(cfg *Cfg) {
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_ENDPOINT")); v != "" {
|
set := func(k, env string, dst *string) {
|
||||||
cfg.Endpoint = v
|
if v := strings.TrimSpace(os.Getenv(env)); v != "" {
|
||||||
cfg.Raw["endpoint"] = v
|
*dst = v
|
||||||
|
cfg.Raw[k] = v
|
||||||
}
|
}
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_MODEL")); v != "" {
|
|
||||||
cfg.Model = v
|
|
||||||
cfg.Raw["model"] = v
|
|
||||||
}
|
}
|
||||||
|
setNum := func(k, env string, dst *int) {
|
||||||
|
if v := strings.TrimSpace(os.Getenv(env)); v != "" {
|
||||||
|
*dst = atoiD(v, *dst)
|
||||||
|
cfg.Raw[k] = strconv.Itoa(*dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set("endpoint", "BANTAM_ENDPOINT", &cfg.Endpoint)
|
||||||
|
set("model", "BANTAM_MODEL", &cfg.Model)
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_TEMP")); v != "" {
|
if v := strings.TrimSpace(os.Getenv("BANTAM_TEMP")); v != "" {
|
||||||
if f, e := strconv.ParseFloat(v, 64); e == nil {
|
if f, e := strconv.ParseFloat(v, 64); e == nil {
|
||||||
cfg.Temperature = f
|
cfg.Temperature = f
|
||||||
@@ -309,38 +300,20 @@ func applyEnvCfg(cfg *Cfg) {
|
|||||||
cfg.Stream = v == "true" || v == "1" || v == "yes"
|
cfg.Stream = v == "true" || v == "1" || v == "yes"
|
||||||
cfg.Raw["stream"] = strconv.FormatBool(cfg.Stream)
|
cfg.Raw["stream"] = strconv.FormatBool(cfg.Stream)
|
||||||
}
|
}
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_COLOR")); v != "" {
|
set("color", "BANTAM_COLOR", &cfg.Color)
|
||||||
cfg.Color = v
|
setNum("timeout", "BANTAM_TIMEOUT", &cfg.Timeout)
|
||||||
cfg.Raw["color"] = v
|
setNum("shell_timeout", "BANTAM_SHELL_TIMEOUT", &cfg.ShellTimeout)
|
||||||
|
setNum("max_al_iterations", "BANTAM_MAX_AL_ITERATIONS", &cfg.MaxALIterations)
|
||||||
|
setNum("context_window", "BANTAM_CONTEXT_WINDOW", &cfg.ContextWindow)
|
||||||
|
setNum("max_tool_res", "BANTAM_MAX_TOOL_RES", &cfg.MaxToolRes)
|
||||||
|
for _, pair := range [][2]string{
|
||||||
|
{"reasoning_effort", "BANTAM_REASONING_EFFORT"},
|
||||||
|
{"bantam_tools_dir", "BANTAM_TOOLS_DIR"},
|
||||||
|
{"bantam_skills_dir", "BANTAM_SKILLS_DIR"},
|
||||||
|
} {
|
||||||
|
if v := strings.TrimSpace(os.Getenv(pair[1])); v != "" {
|
||||||
|
cfg.Raw[pair[0]] = v
|
||||||
}
|
}
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_TIMEOUT")); v != "" {
|
|
||||||
cfg.Timeout = atoiD(v, cfg.Timeout)
|
|
||||||
cfg.Raw["timeout"] = strconv.Itoa(cfg.Timeout)
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_SHELL_TIMEOUT")); v != "" {
|
|
||||||
cfg.ShellTimeout = atoiD(v, cfg.ShellTimeout)
|
|
||||||
cfg.Raw["shell_timeout"] = strconv.Itoa(cfg.ShellTimeout)
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_MAX_AL_ITERATIONS")); v != "" {
|
|
||||||
cfg.MaxALIterations = atoiD(v, cfg.MaxALIterations)
|
|
||||||
cfg.Raw["max_al_iterations"] = strconv.Itoa(cfg.MaxALIterations)
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_CONTEXT_WINDOW")); v != "" {
|
|
||||||
cfg.ContextWindow = atoiD(v, cfg.ContextWindow)
|
|
||||||
cfg.Raw["context_window"] = strconv.Itoa(cfg.ContextWindow)
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_MAX_TOOL_RES")); v != "" {
|
|
||||||
cfg.MaxToolRes = atoiD(v, cfg.MaxToolRes)
|
|
||||||
cfg.Raw["max_tool_res"] = strconv.Itoa(cfg.MaxToolRes)
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_REASONING_EFFORT")); v != "" {
|
|
||||||
cfg.Raw["reasoning_effort"] = v
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_TOOLS_DIR")); v != "" {
|
|
||||||
cfg.Raw["bantam_tools_dir"] = v
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_SKILLS_DIR")); v != "" {
|
|
||||||
cfg.Raw["bantam_skills_dir"] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,28 +412,21 @@ When generating code:
|
|||||||
- No emojis in code or documentation.
|
- No emojis in code or documentation.
|
||||||
- Respect AGENTS.md contents in the project.`
|
- Respect AGENTS.md contents in the project.`
|
||||||
|
|
||||||
|
func cfgOrEnv(cfg *Cfg, key, env string) string {
|
||||||
|
if v := strings.TrimSpace(cfg.Raw[key]); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(os.Getenv(env))
|
||||||
|
}
|
||||||
|
|
||||||
func toolsDir(cfg *Cfg) string {
|
func toolsDir(cfg *Cfg) string {
|
||||||
// The config key takes priority over the environment variable: a local
|
return cfgOrEnv(cfg, "bantam_tools_dir", "BANTAM_TOOLS_DIR")
|
||||||
// .bantam.cfg always overrides BANTAM_TOOLS_DIR.
|
|
||||||
if v := strings.TrimSpace(cfg.Raw["bantam_tools_dir"]); v != "" {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_TOOLS_DIR")); v != "" {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// skillsDir returns the directory Bantam should scan for skills, preferring the
|
// skillsDir returns the directory Bantam should scan for skills, preferring the
|
||||||
// bantam_skills_dir config key, then the BANTAM_SKILLS_DIR environment variable.
|
// bantam_skills_dir config key, then the BANTAM_SKILLS_DIR environment variable.
|
||||||
func skillsDir(cfg *Cfg) string {
|
func skillsDir(cfg *Cfg) string {
|
||||||
if v := strings.TrimSpace(cfg.Raw["bantam_skills_dir"]); v != "" {
|
return cfgOrEnv(cfg, "bantam_skills_dir", "BANTAM_SKILLS_DIR")
|
||||||
return v
|
|
||||||
}
|
|
||||||
if v := strings.TrimSpace(os.Getenv("BANTAM_SKILLS_DIR")); v != "" {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func c(t string, cs ...int) string {
|
func c(t string, cs ...int) string {
|
||||||
@@ -598,9 +564,6 @@ func renderMDLine(line string, st *mdState) string {
|
|||||||
indent := strings.Repeat(" ", len(line)-len(strings.TrimLeft(line, " ")))
|
indent := strings.Repeat(" ", len(line)-len(strings.TrimLeft(line, " ")))
|
||||||
return indent + c(m[1]+" ", 33) + renderInline(m[2])
|
return indent + c(m[1]+" ", 33) + renderInline(m[2])
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(trimmed, "|") && strings.HasSuffix(trimmed, "|") {
|
|
||||||
return renderInline(line)
|
|
||||||
}
|
|
||||||
return renderInline(line)
|
return renderInline(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,11 +836,11 @@ func renderTable(lines []string) []string {
|
|||||||
|
|
||||||
var res []string
|
var res []string
|
||||||
|
|
||||||
var topParts []string
|
var horiz []string
|
||||||
for _, w := range colWidths {
|
for _, w := range colWidths {
|
||||||
topParts = append(topParts, strings.Repeat("─", w+2))
|
horiz = append(horiz, strings.Repeat("─", w+2))
|
||||||
}
|
}
|
||||||
res = append(res, c("┌"+strings.Join(topParts, "┬")+"┐", 2))
|
res = append(res, c("┌"+strings.Join(horiz, "┬")+"┐", 2))
|
||||||
|
|
||||||
if hasHeader {
|
if hasHeader {
|
||||||
headerCols := make([][]string, numCols)
|
headerCols := make([][]string, numCols)
|
||||||
@@ -904,11 +867,7 @@ func renderTable(lines []string) []string {
|
|||||||
res = append(res, c("│", 2)+strings.Join(hCells, c("│", 2))+c("│", 2))
|
res = append(res, c("│", 2)+strings.Join(hCells, c("│", 2))+c("│", 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
var midParts []string
|
res = append(res, c("├"+strings.Join(horiz, "┼")+"┤", 2))
|
||||||
for _, w := range colWidths {
|
|
||||||
midParts = append(midParts, strings.Repeat("─", w+2))
|
|
||||||
}
|
|
||||||
res = append(res, c("├"+strings.Join(midParts, "┼")+"┤", 2))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
@@ -937,11 +896,7 @@ func renderTable(lines []string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var botParts []string
|
res = append(res, c("└"+strings.Join(horiz, "┴")+"┘", 2))
|
||||||
for _, w := range colWidths {
|
|
||||||
botParts = append(botParts, strings.Repeat("─", w+2))
|
|
||||||
}
|
|
||||||
res = append(res, c("└"+strings.Join(botParts, "┴")+"┘", 2))
|
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -963,17 +918,7 @@ func renderMD(text string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ln := range lines {
|
for _, ln := range lines {
|
||||||
trimmed := strings.TrimSpace(ln)
|
if !st.inCode && !strings.HasPrefix(strings.TrimSpace(ln), "```") && isTableLine(ln) {
|
||||||
if strings.HasPrefix(trimmed, "```") {
|
|
||||||
flushTable()
|
|
||||||
out = append(out, renderMDLine(ln, &st))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if st.inCode {
|
|
||||||
out = append(out, renderMDLine(ln, &st))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isTableLine(ln) {
|
|
||||||
tbl = append(tbl, ln)
|
tbl = append(tbl, ln)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1115,21 +1060,18 @@ func contextPct(u Usage, cw int) float64 {
|
|||||||
func formatUsage(u Usage, cw int) string {
|
func formatUsage(u Usage, cw int) string {
|
||||||
base := ctxTokens(u)
|
base := ctxTokens(u)
|
||||||
pct := float64(base) * 100.0 / float64(cw)
|
pct := float64(base) * 100.0 / float64(cw)
|
||||||
cached := u.Cached()
|
pfx := ""
|
||||||
if cached > 0 {
|
if u.Model != "" {
|
||||||
|
pfx = u.Model + ": "
|
||||||
|
}
|
||||||
|
if cached := u.Cached(); cached > 0 {
|
||||||
uncached := u.PromptTokens - cached
|
uncached := u.PromptTokens - cached
|
||||||
if uncached < 0 {
|
if uncached < 0 {
|
||||||
uncached = 0
|
uncached = 0
|
||||||
}
|
}
|
||||||
if u.Model != "" {
|
return fmt.Sprintf("[%s%d prompt (%d cached, %d uncached) + %d completion | context: %d/%d (%.1f%%)]", pfx, u.PromptTokens, cached, uncached, u.CompletionTokens, base, cw, pct)
|
||||||
return fmt.Sprintf("[%s: %d prompt (%d cached, %d uncached) + %d completion | context: %d/%d (%.1f%%)]", u.Model, u.PromptTokens, cached, uncached, u.CompletionTokens, base, cw, pct)
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%d prompt (%d cached, %d uncached) + %d completion | context: %d/%d (%.1f%%)]", u.PromptTokens, cached, uncached, u.CompletionTokens, base, cw, pct)
|
return fmt.Sprintf("[%s%d prompt + %d completion | context: %d/%d (%.1f%%)]", pfx, u.PromptTokens, u.CompletionTokens, base, cw, pct)
|
||||||
}
|
|
||||||
if u.Model != "" {
|
|
||||||
return fmt.Sprintf("[%s: %d prompt + %d completion | context: %d/%d (%.1f%%)]", u.Model, u.PromptTokens, u.CompletionTokens, base, cw, pct)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%d prompt + %d completion | context: %d/%d (%.1f%%)]", u.PromptTokens, u.CompletionTokens, base, cw, pct)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamDelta struct {
|
type streamDelta struct {
|
||||||
@@ -1180,12 +1122,9 @@ func filterText(s string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeMessages(msgs []Message) {
|
func sanitizeToolCalls(tcs []ToolCall) {
|
||||||
for i := range msgs {
|
for j := range tcs {
|
||||||
if msgs[i].Role == "assistant" {
|
tc := &tcs[j]
|
||||||
if len(msgs[i].ToolCalls) > 0 {
|
|
||||||
for j := range msgs[i].ToolCalls {
|
|
||||||
tc := &msgs[i].ToolCalls[j]
|
|
||||||
tc.Function.Arguments = filterText(tc.Function.Arguments)
|
tc.Function.Arguments = filterText(tc.Function.Arguments)
|
||||||
astr := tc.Function.Arguments
|
astr := tc.Function.Arguments
|
||||||
var a map[string]any
|
var a map[string]any
|
||||||
@@ -1195,6 +1134,11 @@ func sanitizeMessages(msgs []Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitizeMessages(msgs []Message) {
|
||||||
|
for i := range msgs {
|
||||||
|
if msgs[i].Role == "assistant" {
|
||||||
|
sanitizeToolCalls(msgs[i].ToolCalls)
|
||||||
if msgs[i].ReasoningContent != "" {
|
if msgs[i].ReasoningContent != "" {
|
||||||
msgs[i].ReasoningContent = filterText(msgs[i].ReasoningContent)
|
msgs[i].ReasoningContent = filterText(msgs[i].ReasoningContent)
|
||||||
}
|
}
|
||||||
@@ -1234,7 +1178,6 @@ func llm(ctx context.Context, cfg *Cfg, msgs []Message, tools []map[string]any)
|
|||||||
if internalKey(k) {
|
if internalKey(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
{
|
|
||||||
var jv any
|
var jv any
|
||||||
if err := json.Unmarshal([]byte(v), &jv); err == nil {
|
if err := json.Unmarshal([]byte(v), &jv); err == nil {
|
||||||
p[k] = jv
|
p[k] = jv
|
||||||
@@ -1242,7 +1185,6 @@ func llm(ctx context.Context, cfg *Cfg, msgs []Message, tools []map[string]any)
|
|||||||
p[k] = v
|
p[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
body, _ := json.Marshal(p)
|
body, _ := json.Marshal(p)
|
||||||
llmTransport.ResponseHeaderTimeout = time.Duration(cfg.Timeout) * time.Second
|
llmTransport.ResponseHeaderTimeout = time.Duration(cfg.Timeout) * time.Second
|
||||||
client := &http.Client{Transport: llmTransport}
|
client := &http.Client{Transport: llmTransport}
|
||||||
@@ -1503,13 +1445,7 @@ func parseStream(ctx context.Context, r io.Reader) (Message, Usage, error) {
|
|||||||
}
|
}
|
||||||
curLine := lineBuf[:idx]
|
curLine := lineBuf[:idx]
|
||||||
lineBuf = lineBuf[idx+1:]
|
lineBuf = lineBuf[idx+1:]
|
||||||
trimmed := strings.TrimSpace(curLine)
|
if !mdSt.inCode && !strings.HasPrefix(strings.TrimSpace(curLine), "```") && isTableLine(curLine) {
|
||||||
if strings.HasPrefix(trimmed, "```") {
|
|
||||||
flushTable()
|
|
||||||
fmt.Println(renderMDLine(curLine, &mdSt))
|
|
||||||
} else if mdSt.inCode {
|
|
||||||
fmt.Println(renderMDLine(curLine, &mdSt))
|
|
||||||
} else if isTableLine(curLine) {
|
|
||||||
tblBuf = append(tblBuf, curLine)
|
tblBuf = append(tblBuf, curLine)
|
||||||
} else {
|
} else {
|
||||||
flushTable()
|
flushTable()
|
||||||
@@ -1663,14 +1599,9 @@ func writeFile(path string, offset, delBytes int, content string) (string, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
} else {
|
} else if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if dir != "" && dir != "." {
|
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if offset > len(data) {
|
if offset > len(data) {
|
||||||
padding := make([]byte, offset-len(data))
|
padding := make([]byte, offset-len(data))
|
||||||
data = append(data, padding...)
|
data = append(data, padding...)
|
||||||
@@ -1711,16 +1642,13 @@ func readFileOrDir(path string, offset, limit int) (string, int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("[tool error: read %s: %v]", path, err), 31
|
return fmt.Sprintf("[tool error: read %s: %v]", path, err), 31
|
||||||
}
|
}
|
||||||
sort.Slice(entries, func(i, j int) bool {
|
|
||||||
return entries[i].Name() < entries[j].Name()
|
|
||||||
})
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
|
b.WriteString(e.Name())
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
b.WriteString(e.Name() + "/\n")
|
b.WriteByte('/')
|
||||||
} else {
|
|
||||||
b.WriteString(e.Name() + "\n")
|
|
||||||
}
|
}
|
||||||
|
b.WriteByte('\n')
|
||||||
}
|
}
|
||||||
return strings.TrimRight(b.String(), "\n"), 2
|
return strings.TrimRight(b.String(), "\n"), 2
|
||||||
}
|
}
|
||||||
@@ -1766,13 +1694,17 @@ func readFileOrDir(path string, offset, limit int) (string, int) {
|
|||||||
return strings.TrimRight(b.String(), "\n"), 2
|
return strings.TrimRight(b.String(), "\n"), 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// toolResDir returns the directory where oversized tool results are spilled.
|
func bantamTmpDir() string {
|
||||||
func toolResDir() string {
|
|
||||||
tmp := os.Getenv("TMPDIR")
|
tmp := os.Getenv("TMPDIR")
|
||||||
if tmp == "" {
|
if tmp == "" {
|
||||||
tmp = "/tmp"
|
tmp = "/tmp"
|
||||||
}
|
}
|
||||||
return filepath.Join(tmp, "bantam", "toolres")
|
return filepath.Join(tmp, "bantam")
|
||||||
|
}
|
||||||
|
|
||||||
|
// toolResDir returns the directory where oversized tool results are spilled.
|
||||||
|
func toolResDir() string {
|
||||||
|
return filepath.Join(bantamTmpDir(), "toolres")
|
||||||
}
|
}
|
||||||
|
|
||||||
// offloadToolResult keeps tool results that fit within maxRes bytes inline.
|
// offloadToolResult keeps tool results that fit within maxRes bytes inline.
|
||||||
@@ -1818,6 +1750,14 @@ func cleanupTemps(files []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toolPath(a map[string]any) string {
|
||||||
|
p, _ := a["filePath"].(string)
|
||||||
|
if p == "" {
|
||||||
|
p, _ = a["path"].(string)
|
||||||
|
}
|
||||||
|
return filterText(p)
|
||||||
|
}
|
||||||
|
|
||||||
func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error) {
|
func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error) {
|
||||||
done := false
|
done := false
|
||||||
var turnUsage Usage
|
var turnUsage Usage
|
||||||
@@ -1860,16 +1800,7 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error)
|
|||||||
if u.Model != "" {
|
if u.Model != "" {
|
||||||
turnUsage.Model = u.Model
|
turnUsage.Model = u.Model
|
||||||
}
|
}
|
||||||
for j := range m.ToolCalls {
|
sanitizeToolCalls(m.ToolCalls)
|
||||||
tc := &m.ToolCalls[j]
|
|
||||||
tc.Function.Arguments = filterText(tc.Function.Arguments)
|
|
||||||
astr := tc.Function.Arguments
|
|
||||||
var a map[string]any
|
|
||||||
if err := json.Unmarshal([]byte(astr), &a); err != nil || a == nil {
|
|
||||||
fixed, _ := json.Marshal(map[string]string{"invalid_raw": astr})
|
|
||||||
tc.Function.Arguments = string(fixed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msgs = append(msgs, m)
|
msgs = append(msgs, m)
|
||||||
if !cfg.Stream {
|
if !cfg.Stream {
|
||||||
if m.ReasoningContent != "" {
|
if m.ReasoningContent != "" {
|
||||||
@@ -1910,65 +1841,21 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error)
|
|||||||
workdir, _ := a["workdir"].(string)
|
workdir, _ := a["workdir"].(string)
|
||||||
workdir = filterText(workdir)
|
workdir = filterText(workdir)
|
||||||
to := cfg.ShellTimeout
|
to := cfg.ShellTimeout
|
||||||
if v, ok := a["timeout"]; ok && v != nil {
|
if n := toInt(a["timeout"], 0); n > 0 {
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
if n > 0 {
|
|
||||||
if n > 1000 {
|
|
||||||
to = int(n / 1000)
|
|
||||||
} else {
|
|
||||||
to = int(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case int:
|
|
||||||
if n > 0 {
|
|
||||||
if n > 1000 {
|
if n > 1000 {
|
||||||
to = n / 1000
|
to = n / 1000
|
||||||
} else {
|
} else {
|
||||||
to = n
|
to = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
res = shellWithWorkdir(ctx, cmd, workdir, to)
|
res = shellWithWorkdir(ctx, cmd, workdir, to)
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return msgs, turnUsage, err
|
return msgs, turnUsage, err
|
||||||
}
|
}
|
||||||
case "read":
|
case "read":
|
||||||
path, _ := a["filePath"].(string)
|
res, sty = readFileOrDir(toolPath(a), toInt(a["offset"], 1), toInt(a["limit"], 2000))
|
||||||
if path == "" {
|
|
||||||
path, _ = a["path"].(string)
|
|
||||||
}
|
|
||||||
path = filterText(path)
|
|
||||||
offset := 1
|
|
||||||
if v, ok := a["offset"]; ok && v != nil {
|
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
offset = int(n)
|
|
||||||
case int:
|
|
||||||
offset = n
|
|
||||||
case string:
|
|
||||||
offset = atoiD(n, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
limit := 2000
|
|
||||||
if v, ok := a["limit"]; ok && v != nil {
|
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
limit = int(n)
|
|
||||||
case int:
|
|
||||||
limit = n
|
|
||||||
case string:
|
|
||||||
limit = atoiD(n, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res, sty = readFileOrDir(path, offset, limit)
|
|
||||||
case "write", "write_file":
|
case "write", "write_file":
|
||||||
path, _ := a["filePath"].(string)
|
path := toolPath(a)
|
||||||
if path == "" {
|
|
||||||
path, _ = a["path"].(string)
|
|
||||||
}
|
|
||||||
path = filterText(path)
|
|
||||||
contentVal, hasContent := a["content"]
|
contentVal, hasContent := a["content"]
|
||||||
var content string
|
var content string
|
||||||
if hasContent && contentVal != nil {
|
if hasContent && contentVal != nil {
|
||||||
@@ -1976,63 +1863,32 @@ func AL(ctx context.Context, cfg *Cfg, msgs []Message) ([]Message, Usage, error)
|
|||||||
content = s
|
content = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasOffset := false
|
hasOffset := a["offset"] != nil
|
||||||
offset := 0
|
offset := toInt(a["offset"], 0)
|
||||||
if v, ok := a["offset"]; ok && v != nil {
|
|
||||||
hasOffset = true
|
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
offset = int(n)
|
|
||||||
case int:
|
|
||||||
offset = n
|
|
||||||
case string:
|
|
||||||
offset = atoiD(n, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
offset = 0
|
offset = 0
|
||||||
}
|
}
|
||||||
hasDel := false
|
hasDel := a["del_bytes"] != nil
|
||||||
delBytes := 0
|
delBytes := toInt(a["del_bytes"], 0)
|
||||||
if v, ok := a["del_bytes"]; ok {
|
|
||||||
hasDel = true
|
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
delBytes = int(n)
|
|
||||||
case int:
|
|
||||||
delBytes = n
|
|
||||||
case string:
|
|
||||||
delBytes = atoiD(n, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(path) == "" {
|
if strings.TrimSpace(path) == "" {
|
||||||
res, sty = fmt.Sprintf("[tool error: %s requires 'filePath' parameter]", fn), 31
|
res, sty = fmt.Sprintf("[tool error: %s requires 'filePath' parameter]", fn), 31
|
||||||
} else if !hasContent {
|
} else if !hasContent {
|
||||||
res, sty = fmt.Sprintf("[tool error: %s requires 'content' parameter]", fn), 31
|
res, sty = fmt.Sprintf("[tool error: %s requires 'content' parameter]", fn), 31
|
||||||
} else if !hasOffset && !hasDel {
|
} else if !hasOffset && !hasDel {
|
||||||
// Neither offset nor del_bytes was supplied: overwrite the entire
|
|
||||||
// file with the new content. The LLM usually just wants to replace a
|
|
||||||
// file and should not have to know about the tool's offset quirks;
|
|
||||||
// insertion/replace semantics are preserved when either is given.
|
|
||||||
p := strings.TrimSpace(path)
|
p := strings.TrimSpace(path)
|
||||||
if dir := filepath.Dir(p); dir != "" && dir != "." {
|
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
||||||
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, p, err), 31
|
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, p, err), 31
|
||||||
}
|
} else if err := os.WriteFile(p, []byte(content), 0644); err != nil {
|
||||||
}
|
|
||||||
if res == "" {
|
|
||||||
if err := os.WriteFile(p, []byte(content), 0644); err != nil {
|
|
||||||
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, p, err), 31
|
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, p, err), 31
|
||||||
} else {
|
} else {
|
||||||
res, sty = fmt.Sprintf("Successfully wrote %d bytes to %s", len(content), p), 2
|
res = fmt.Sprintf("Successfully wrote %d bytes to %s", len(content), p)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out, err := writeFile(path, offset, delBytes, content)
|
out, err := writeFile(path, offset, delBytes, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, path, err), 31
|
res, sty = fmt.Sprintf("[tool error: %s %s: %v]", fn, path, err), 31
|
||||||
} else {
|
} else {
|
||||||
res, sty = out, 2
|
res = out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -2105,6 +1961,21 @@ func projectID() string {
|
|||||||
// timestamp prefix (6 bytes, big endian) and a 14-character random Base62 tail.
|
// timestamp prefix (6 bytes, big endian) and a 14-character random Base62 tail.
|
||||||
// When descending is true (session IDs), the timestamp value is bitwise inverted (~$),
|
// When descending is true (session IDs), the timestamp value is bitwise inverted (~$),
|
||||||
// ensuring session IDs and request IDs form bitwise inverse hex prefixes.
|
// ensuring session IDs and request IDs form bitwise inverse hex prefixes.
|
||||||
|
func ocRandomTail() string {
|
||||||
|
buf := make([]byte, 14)
|
||||||
|
if _, err := rand.Read(buf); err != nil {
|
||||||
|
h := md5.Sum([]byte(fmt.Sprintf("%d-%d", time.Now().UnixNano(), os.Getpid())))
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = h[i%len(h)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tail strings.Builder
|
||||||
|
for _, b := range buf {
|
||||||
|
tail.WriteByte(opencodeTailAlphabet[int(b)%len(opencodeTailAlphabet)])
|
||||||
|
}
|
||||||
|
return tail.String()
|
||||||
|
}
|
||||||
|
|
||||||
func genOpencodeID(prefix string, descending bool) string {
|
func genOpencodeID(prefix string, descending bool) string {
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
ocMu.Lock()
|
ocMu.Lock()
|
||||||
@@ -2121,19 +1992,7 @@ func genOpencodeID(prefix string, descending bool) string {
|
|||||||
val = ^val
|
val = ^val
|
||||||
}
|
}
|
||||||
hexPart := fmt.Sprintf("%012x", val&0xffffffffffff)
|
hexPart := fmt.Sprintf("%012x", val&0xffffffffffff)
|
||||||
|
return prefix + hexPart + ocRandomTail()
|
||||||
buf := make([]byte, 14)
|
|
||||||
if _, err := rand.Read(buf); err != nil {
|
|
||||||
h := md5.Sum([]byte(fmt.Sprintf("%d-%d", time.Now().UnixNano(), os.Getpid())))
|
|
||||||
for i := range buf {
|
|
||||||
buf[i] = h[i%len(h)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tail strings.Builder
|
|
||||||
for _, b := range buf {
|
|
||||||
tail.WriteByte(opencodeTailAlphabet[int(b)%len(opencodeTailAlphabet)])
|
|
||||||
}
|
|
||||||
return prefix + hexPart + tail.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func opencodeSessionID() string {
|
func opencodeSessionID() string {
|
||||||
@@ -2144,22 +2003,8 @@ func opencodeSessionID() string {
|
|||||||
// is the exact bitwise inverse of the given msg_ request ID.
|
// is the exact bitwise inverse of the given msg_ request ID.
|
||||||
func opencodeSessionIDFromRequest(msgID string) string {
|
func opencodeSessionIDFromRequest(msgID string) string {
|
||||||
if strings.HasPrefix(msgID, "msg_") && len(msgID) >= 16 {
|
if strings.HasPrefix(msgID, "msg_") && len(msgID) >= 16 {
|
||||||
hexPart := msgID[4:16]
|
if val, err := strconv.ParseUint(msgID[4:16], 16, 64); err == nil {
|
||||||
if val, err := strconv.ParseUint(hexPart, 16, 64); err == nil {
|
return fmt.Sprintf("ses_%012x", (^val)&0xffffffffffff) + ocRandomTail()
|
||||||
invVal := (^val) & 0xffffffffffff
|
|
||||||
invHex := fmt.Sprintf("%012x", invVal)
|
|
||||||
buf := make([]byte, 14)
|
|
||||||
if _, err := rand.Read(buf); err != nil {
|
|
||||||
h := md5.Sum([]byte(fmt.Sprintf("%d-%d", time.Now().UnixNano(), os.Getpid())))
|
|
||||||
for i := range buf {
|
|
||||||
buf[i] = h[i%len(h)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tail strings.Builder
|
|
||||||
for _, b := range buf {
|
|
||||||
tail.WriteByte(opencodeTailAlphabet[int(b)%len(opencodeTailAlphabet)])
|
|
||||||
}
|
|
||||||
return "ses_" + invHex + tail.String()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return genOpencodeID("ses_", true)
|
return genOpencodeID("ses_", true)
|
||||||
@@ -2692,12 +2537,8 @@ func skillPrompt(u string, cfg *Cfg) (string, error) {
|
|||||||
if rest == "" {
|
if rest == "" {
|
||||||
return "", fmt.Errorf("usage: /skill <skill_name|absolute_path> [prompt]")
|
return "", fmt.Errorf("usage: /skill <skill_name|absolute_path> [prompt]")
|
||||||
}
|
}
|
||||||
fields := strings.SplitN(rest, " ", 2)
|
name, prompt, _ := strings.Cut(rest, " ")
|
||||||
name := fields[0]
|
prompt = strings.TrimSpace(prompt)
|
||||||
prompt := ""
|
|
||||||
if len(fields) == 2 {
|
|
||||||
prompt = strings.TrimSpace(fields[1])
|
|
||||||
}
|
|
||||||
sd := skillsDir(cfg)
|
sd := skillsDir(cfg)
|
||||||
var path string
|
var path string
|
||||||
if sd != "" {
|
if sd != "" {
|
||||||
@@ -2791,14 +2632,31 @@ func doCompact(cfg *Cfg, msgs []Message) []Message {
|
|||||||
// $TMPDIR/bantam placeholder to a concrete temporary directory, substitutes the
|
// $TMPDIR/bantam placeholder to a concrete temporary directory, substitutes the
|
||||||
// configured max_tool_res value, and appends the optional extra-tools and
|
// configured max_tool_res value, and appends the optional extra-tools and
|
||||||
// skills hints.
|
// skills hints.
|
||||||
func buildSystemPrompt(cfg *Cfg) string {
|
func runTurn(cfg *Cfg, msgs []Message) ([]Message, Usage, error) {
|
||||||
sp := defaultSystemPrompt
|
sigCtx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
tmp := os.Getenv("TMPDIR")
|
defer cancel()
|
||||||
if tmp == "" {
|
resMsgs, usg, err := AL(sigCtx, cfg, msgs)
|
||||||
tmp = "/tmp"
|
if lastRole(resMsgs) == "tool" {
|
||||||
|
resMsgs = append(resMsgs, Message{Role: "user", Content: strp("continue")})
|
||||||
|
fmt.Println(c("[auto continue: last message was a tool result]", 33))
|
||||||
|
resMsgs, usg, err = AL(sigCtx, cfg, resMsgs)
|
||||||
}
|
}
|
||||||
bantamTmp := filepath.Join(tmp, "bantam")
|
if err != nil {
|
||||||
sp = strings.ReplaceAll(sp, "$TMPDIR/bantam", bantamTmp)
|
if sigCtx.Err() != nil || errors.Is(err, context.Canceled) {
|
||||||
|
fmt.Println(c("\n[interrupted]", 33))
|
||||||
|
} else {
|
||||||
|
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resMsgs, usg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSystemPrompt renders the built-in system prompt for cfg: it expands the
|
||||||
|
// $TMPDIR/bantam placeholder to a concrete temporary directory, substitutes the
|
||||||
|
// configured max_tool_res value, and appends the optional extra-tools and
|
||||||
|
// skills hints.
|
||||||
|
func buildSystemPrompt(cfg *Cfg) string {
|
||||||
|
sp := strings.ReplaceAll(defaultSystemPrompt, "$TMPDIR/bantam", bantamTmpDir())
|
||||||
sp = strings.ReplaceAll(sp, "$MAX_TOOL_RES", strconv.Itoa(cfg.MaxToolRes))
|
sp = strings.ReplaceAll(sp, "$MAX_TOOL_RES", strconv.Itoa(cfg.MaxToolRes))
|
||||||
if td := toolsDir(cfg); td != "" {
|
if td := toolsDir(cfg); td != "" {
|
||||||
sp += "\n\nExtra shell tools can be found at " + td
|
sp += "\n\nExtra shell tools can be found at " + td
|
||||||
@@ -2829,22 +2687,9 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
msgs = append(msgs, Message{Role: "user", Content: strp(u)})
|
msgs = append(msgs, Message{Role: "user", Content: strp(u)})
|
||||||
sigCtx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
||||||
var usg Usage
|
var usg Usage
|
||||||
msgs, usg, err = AL(sigCtx, &cfg, msgs)
|
msgs, usg, err = runTurn(&cfg, msgs)
|
||||||
if lastRole(msgs) == "tool" {
|
|
||||||
msgs = append(msgs, Message{Role: "user", Content: strp("continue")})
|
|
||||||
fmt.Println(c("[auto continue: last message was a tool result]", 33))
|
|
||||||
msgs, usg, err = AL(sigCtx, &cfg, msgs)
|
|
||||||
}
|
|
||||||
interrupted := sigCtx.Err() != nil
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if interrupted || errors.Is(err, context.Canceled) {
|
|
||||||
fmt.Println(c("\n[interrupted]", 33))
|
|
||||||
} else {
|
|
||||||
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println(c(formatUsage(usg, cfg.ContextWindow), 2))
|
fmt.Println(c(formatUsage(usg, cfg.ContextWindow), 2))
|
||||||
@@ -2887,14 +2732,12 @@ func main() {
|
|||||||
addHistory(u)
|
addHistory(u)
|
||||||
// Command aliases: /model -> /cfg model, /endpoint -> /cfg endpoint.
|
// Command aliases: /model -> /cfg model, /endpoint -> /cfg endpoint.
|
||||||
// (Note: /models is a distinct command and is intentionally not matched.)
|
// (Note: /models is a distinct command and is intentionally not matched.)
|
||||||
if u == "/model" {
|
for _, cmd := range []string{"model", "endpoint"} {
|
||||||
u = "/cfg model"
|
if u == "/"+cmd {
|
||||||
} else if strings.HasPrefix(u, "/model ") {
|
u = "/cfg " + cmd
|
||||||
u = "/cfg model " + strings.TrimSpace(strings.TrimPrefix(u, "/model "))
|
} else if strings.HasPrefix(u, "/"+cmd+" ") {
|
||||||
} else if u == "/endpoint" {
|
u = "/cfg " + cmd + " " + strings.TrimSpace(u[len(cmd)+2:])
|
||||||
u = "/cfg endpoint"
|
}
|
||||||
} else if strings.HasPrefix(u, "/endpoint ") {
|
|
||||||
u = "/cfg endpoint " + strings.TrimSpace(strings.TrimPrefix(u, "/endpoint "))
|
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case u == "/quit":
|
case u == "/quit":
|
||||||
@@ -3023,23 +2866,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
turnMsgs := append([]Message{}, msgs...)
|
turnMsgs := append(append([]Message{}, msgs...), Message{Role: "user", Content: strp(u)})
|
||||||
turnMsgs = append(turnMsgs, Message{Role: "user", Content: strp(u)})
|
resMsgs, usg, err := runTurn(&cfg, turnMsgs)
|
||||||
sigCtx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
||||||
resMsgs, usg, err := AL(sigCtx, &cfg, turnMsgs)
|
|
||||||
if lastRole(resMsgs) == "tool" {
|
|
||||||
resMsgs = append(resMsgs, Message{Role: "user", Content: strp("continue")})
|
|
||||||
fmt.Println(c("[auto continue: last message was a tool result]", 33))
|
|
||||||
resMsgs, usg, err = AL(sigCtx, &cfg, resMsgs)
|
|
||||||
}
|
|
||||||
interrupted := sigCtx.Err() != nil
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if interrupted || errors.Is(err, context.Canceled) {
|
|
||||||
fmt.Println(c("\n[interrupted]", 33))
|
|
||||||
} else {
|
|
||||||
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msgs = resMsgs
|
msgs = resMsgs
|
||||||
|
|||||||
Reference in New Issue
Block a user