mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
fix(agent,claw402): harden agent runtime and strip max_tokens for thinking models
- Fix Stop() race condition using sync.Once - Add ensureHistory() to prevent nil panic in planner/dispatcher - Add bounds check on trader ID slicing - Log saveExecutionState and clearSetupState errors instead of discarding - Remove always-true modelID condition in onboard setup - Add Chinese setup keywords and expand model name aliases - Strip max_tokens from claw402 requests to avoid thinking-model budget exhaustion - Hide Agent nav tab (Beta) pending merge to main - Sync tests with code changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+27
-8
@@ -14,6 +14,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"nofx/manager"
|
"nofx/manager"
|
||||||
@@ -34,6 +35,7 @@ type Agent struct {
|
|||||||
history *chatHistory
|
history *chatHistory
|
||||||
pending *pendingTrades
|
pending *pendingTrades
|
||||||
stopCh chan struct{} // signals background goroutines to stop
|
stopCh chan struct{} // signals background goroutines to stop
|
||||||
|
stopOnce sync.Once
|
||||||
NotifyFunc func(userID int64, text string) error
|
NotifyFunc func(userID int64, text string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +64,12 @@ func New(tm *manager.TraderManager, st *store.Store, cfg *Config, logger *slog.L
|
|||||||
|
|
||||||
func (a *Agent) SetAIClient(c mcp.AIClient) { a.aiClient = c }
|
func (a *Agent) SetAIClient(c mcp.AIClient) { a.aiClient = c }
|
||||||
|
|
||||||
|
func (a *Agent) ensureHistory() {
|
||||||
|
if a.history == nil {
|
||||||
|
a.history = newChatHistory(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) log() *slog.Logger {
|
func (a *Agent) log() *slog.Logger {
|
||||||
if a != nil && a.logger != nil {
|
if a != nil && a.logger != nil {
|
||||||
return a.logger
|
return a.logger
|
||||||
@@ -121,7 +129,19 @@ func (a *Agent) loadAIClientFromStoreUser(storeUserID string) (mcp.AIClient, str
|
|||||||
apiKey := string(model.APIKey)
|
apiKey := string(model.APIKey)
|
||||||
customAPIURL := strings.TrimSpace(model.CustomAPIURL)
|
customAPIURL := strings.TrimSpace(model.CustomAPIURL)
|
||||||
modelName := strings.TrimSpace(model.CustomModelName)
|
modelName := strings.TrimSpace(model.CustomModelName)
|
||||||
customAPIURL, modelName = resolveModelRuntimeConfig(model.Provider, customAPIURL, modelName, model.ID)
|
provider := strings.ToLower(strings.TrimSpace(model.Provider))
|
||||||
|
|
||||||
|
// Use the provider registry for providers like claw402 that have their own
|
||||||
|
// client implementation (x402 payment, custom auth, etc.).
|
||||||
|
if client := mcp.NewAIClientByProvider(provider); client != nil {
|
||||||
|
if modelName == "" {
|
||||||
|
modelName = model.ID
|
||||||
|
}
|
||||||
|
client.SetAPIKey(apiKey, customAPIURL, modelName)
|
||||||
|
return client, modelName, true
|
||||||
|
}
|
||||||
|
|
||||||
|
customAPIURL, modelName = resolveModelRuntimeConfig(provider, customAPIURL, modelName, model.ID)
|
||||||
if apiKey == "" || customAPIURL == "" {
|
if apiKey == "" || customAPIURL == "" {
|
||||||
a.log().Warn(
|
a.log().Warn(
|
||||||
"enabled AI model is incomplete",
|
"enabled AI model is incomplete",
|
||||||
@@ -201,12 +221,7 @@ func (a *Agent) Start() {
|
|||||||
|
|
||||||
func (a *Agent) Stop() {
|
func (a *Agent) Stop() {
|
||||||
// Signal all background goroutines (e.g. chat-history-cleanup) to exit.
|
// Signal all background goroutines (e.g. chat-history-cleanup) to exit.
|
||||||
select {
|
a.stopOnce.Do(func() { close(a.stopCh) })
|
||||||
case <-a.stopCh:
|
|
||||||
// Already closed
|
|
||||||
default:
|
|
||||||
close(a.stopCh)
|
|
||||||
}
|
|
||||||
if a.sentinel != nil {
|
if a.sentinel != nil {
|
||||||
a.sentinel.Stop()
|
a.sentinel.Stop()
|
||||||
}
|
}
|
||||||
@@ -689,7 +704,11 @@ func (a *Agent) queryPositionsDirect(L string) (string, error) {
|
|||||||
if pnl < 0 {
|
if pnl < 0 {
|
||||||
e = "🔴"
|
e = "🔴"
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("%s *%s* %s — $%.2f | Trader: %s\n", e, p["symbol"], p["side"], pnl, id[:8]))
|
tid := id
|
||||||
|
if len(tid) > 8 {
|
||||||
|
tid = tid[:8]
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%s *%s* %s — $%.2f | Trader: %s\n", e, p["symbol"], p["side"], pnl, tid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasAny {
|
if !hasAny {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"nofx/mcp"
|
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -380,7 +379,9 @@ func TestLoadAIClientFromStoreUserUsesUserSpecificEnabledModel(t *testing.T) {
|
|||||||
t.Fatalf("unexpected model name: %s", modelName)
|
t.Fatalf("unexpected model name: %s", modelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := client.(*mcp.Client); !ok {
|
// After the provider registry refactor, registered providers (like openai)
|
||||||
t.Fatalf("expected *mcp.Client, got %T", client)
|
// return their own AIClient implementation, not *mcp.Client.
|
||||||
|
if client == nil {
|
||||||
|
t.Fatal("expected non-nil AI client from provider registry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -87,7 +87,9 @@ func (a *Agent) saveSetupState(userID int64, s *SetupState) {
|
|||||||
|
|
||||||
func (a *Agent) clearSetupState(userID int64) {
|
func (a *Agent) clearSetupState(userID int64) {
|
||||||
for _, k := range []string{"step", "exchange", "exchange_id", "api_key", "api_secret", "passphrase", "ai_provider", "ai_model", "ai_model_id", "ai_key", "ai_base_url"} {
|
for _, k := range []string{"step", "exchange", "exchange_id", "api_key", "api_secret", "passphrase", "ai_provider", "ai_model", "ai_model_id", "ai_key", "ai_base_url"} {
|
||||||
a.store.SetSystemConfig(fmt.Sprintf("setup_%s_%d", k, userID), "")
|
if err := a.store.SetSystemConfig(fmt.Sprintf("setup_%s_%d", k, userID), ""); err != nil {
|
||||||
|
a.log().Warn("clearSetupState: failed to clear key", "key", k, "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +226,7 @@ func isDirectSetupCommand(text string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch text {
|
switch text {
|
||||||
case "setup", "/setup":
|
case "setup", "/setup", "开始配置", "配置", "开始设置":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -500,9 +502,7 @@ func (a *Agent) saveSetupAIModel(storeUserID string, state *SetupState) (string,
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if modelID == state.AIProvider {
|
modelID = fmt.Sprintf("%s_%s", storeUserID, state.AIProvider)
|
||||||
modelID = fmt.Sprintf("%s_%s", storeUserID, state.AIProvider)
|
|
||||||
}
|
|
||||||
return modelID, nil
|
return modelID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ func TestIsDirectSetupCommand(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{text: "setup", want: true},
|
{text: "setup", want: true},
|
||||||
{text: "/setup", want: true},
|
{text: "/setup", want: true},
|
||||||
{text: "开始配置", want: false},
|
{text: "开始配置", want: true},
|
||||||
|
{text: "配置", want: true},
|
||||||
|
{text: "开始设置", want: true},
|
||||||
{text: "/开始配置", want: false},
|
{text: "/开始配置", want: false},
|
||||||
{text: "创建全新的配置,杠杆你定", want: false},
|
{text: "创建全新的配置,杠杆你定", want: false},
|
||||||
{text: "帮我配置一个 deepseek 模型", want: false},
|
{text: "帮我配置一个 deepseek 模型", want: false},
|
||||||
{text: "绑定交易所 okx", want: false},
|
{text: "绑定交易所 okx", want: false},
|
||||||
{text: "配置", want: false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|||||||
@@ -468,9 +468,7 @@ func (a *Agent) tryReadFastPath(storeUserID string, userID int64, lang, text str
|
|||||||
if req == nil {
|
if req == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
if a.history == nil {
|
a.ensureHistory()
|
||||||
a.history = newChatHistory(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.history.Add(userID, "user", text)
|
a.history.Add(userID, "user", text)
|
||||||
raw := a.executeReadFastPath(storeUserID, userID, req)
|
raw := a.executeReadFastPath(storeUserID, userID, req)
|
||||||
@@ -758,6 +756,10 @@ func (a *Agent) thinkAndAct(ctx context.Context, storeUserID string, userID int6
|
|||||||
if answer, ok := a.tryHardSkill(ctx, storeUserID, userID, lang, text, nil); ok {
|
if answer, ok := a.tryHardSkill(ctx, storeUserID, userID, lang, text, nil); ok {
|
||||||
return answer, nil
|
return answer, nil
|
||||||
}
|
}
|
||||||
|
// Check setup flow before falling back to noAI — handles "开始配置", "setup", etc.
|
||||||
|
if reply, handled := a.handleSetupFlowForStoreUser(storeUserID, userID, text, lang); handled {
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
if a.aiClient == nil {
|
if a.aiClient == nil {
|
||||||
return a.noAIFallback(lang, text)
|
return a.noAIFallback(lang, text)
|
||||||
}
|
}
|
||||||
@@ -787,6 +789,13 @@ func (a *Agent) thinkAndActStream(ctx context.Context, storeUserID string, userI
|
|||||||
if answer, ok := a.tryHardSkill(ctx, storeUserID, userID, lang, text, onEvent); ok {
|
if answer, ok := a.tryHardSkill(ctx, storeUserID, userID, lang, text, onEvent); ok {
|
||||||
return answer, nil
|
return answer, nil
|
||||||
}
|
}
|
||||||
|
// Check setup flow before falling back to noAI — handles "开始配置", "setup", etc.
|
||||||
|
if reply, handled := a.handleSetupFlowForStoreUser(storeUserID, userID, text, lang); handled {
|
||||||
|
if onEvent != nil {
|
||||||
|
onEvent(StreamEventDelta, reply)
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
if a.aiClient == nil {
|
if a.aiClient == nil {
|
||||||
return a.noAIFallback(lang, text)
|
return a.noAIFallback(lang, text)
|
||||||
}
|
}
|
||||||
@@ -1256,9 +1265,7 @@ Return JSON with this exact shape:
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.history == nil {
|
a.ensureHistory()
|
||||||
a.history = newChatHistory(100)
|
|
||||||
}
|
|
||||||
a.history.Add(userID, "user", text)
|
a.history.Add(userID, "user", text)
|
||||||
a.history.Add(userID, "assistant", answer)
|
a.history.Add(userID, "assistant", answer)
|
||||||
a.maybeUpdateTaskStateIncrementally(ctx, userID)
|
a.maybeUpdateTaskStateIncrementally(ctx, userID)
|
||||||
@@ -1297,6 +1304,7 @@ func normalizeDirectReplyDecision(decision directReplyDecision) directReplyDecis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) runPlannedAgent(ctx context.Context, storeUserID string, userID int64, lang, text string, onEvent func(event, data string)) (string, error) {
|
func (a *Agent) runPlannedAgent(ctx context.Context, storeUserID string, userID int64, lang, text string, onEvent func(event, data string)) (string, error) {
|
||||||
|
a.ensureHistory()
|
||||||
a.history.Add(userID, "user", text)
|
a.history.Add(userID, "user", text)
|
||||||
if onEvent != nil {
|
if onEvent != nil {
|
||||||
onEvent(StreamEventPlanning, a.planningStatusText(lang))
|
onEvent(StreamEventPlanning, a.planningStatusText(lang))
|
||||||
@@ -1797,7 +1805,9 @@ func (a *Agent) executePlan(ctx context.Context, storeUserID string, userID int6
|
|||||||
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||||
})
|
})
|
||||||
referencesChanged = updateCurrentReferencesFromToolResult(state, step.ToolName, result)
|
referencesChanged = updateCurrentReferencesFromToolResult(state, step.ToolName, result)
|
||||||
_ = referencesChanged
|
if referencesChanged {
|
||||||
|
a.log().Info("tool step updated references", "tool", step.ToolName, "session", state.SessionID)
|
||||||
|
}
|
||||||
case planStepTypeReason:
|
case planStepTypeReason:
|
||||||
reasonStartedAt := time.Now()
|
reasonStartedAt := time.Now()
|
||||||
reasoning, err := a.executeReasonStep(ctx, userID, lang, state.Goal, *state, *step)
|
reasoning, err := a.executeReasonStep(ctx, userID, lang, state.Goal, *state, *step)
|
||||||
@@ -1807,7 +1817,9 @@ func (a *Agent) executePlan(ctx context.Context, storeUserID string, userID int6
|
|||||||
step.Error = err.Error()
|
step.Error = err.Error()
|
||||||
state.Status = executionStatusFailed
|
state.Status = executionStatusFailed
|
||||||
state.LastError = err.Error()
|
state.LastError = err.Error()
|
||||||
_ = a.saveExecutionState(*state)
|
if saveErr := a.saveExecutionState(*state); saveErr != nil {
|
||||||
|
a.log().Warn("failed to save execution state after reason step error", "error", saveErr)
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
step.Status = planStepStatusCompleted
|
step.Status = planStepStatusCompleted
|
||||||
|
|||||||
@@ -661,9 +661,7 @@ func (a *Agent) tryHardSkill(ctx context.Context, storeUserID string, userID int
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) recordSkillInteraction(userID int64, userText, answer string) {
|
func (a *Agent) recordSkillInteraction(userID int64, userText, answer string) {
|
||||||
if a.history == nil {
|
a.ensureHistory()
|
||||||
a.history = newChatHistory(100)
|
|
||||||
}
|
|
||||||
a.history.Add(userID, "user", userText)
|
a.history.Add(userID, "user", userText)
|
||||||
a.history.Add(userID, "assistant", answer)
|
a.history.Add(userID, "assistant", answer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -677,8 +677,8 @@ func (a *Agent) executeModelManagementAction(storeUserID string, userID int64, l
|
|||||||
if apiKey := extractCredentialValue(text, []string{"api key", "apikey", "api_key"}); apiKey != "" {
|
if apiKey := extractCredentialValue(text, []string{"api key", "apikey", "api_key"}); apiKey != "" {
|
||||||
setField(&session, "api_key", apiKey)
|
setField(&session, "api_key", apiKey)
|
||||||
}
|
}
|
||||||
if modelName := extractPostKeywordName(text, []string{"model name", "模型名", "模型名称", "改成"}); modelName != "" {
|
if modelName := extractPostKeywordName(text, []string{"model name", "模型名", "模型名称", "改成", "改为", "修改为", "换成", "换到", "切换为", "切换到", "change to", "switch to"}); modelName != "" {
|
||||||
setField(&session, "custom_model_name", modelName)
|
setField(&session, "custom_model_name", normalizeModelName(modelName))
|
||||||
}
|
}
|
||||||
if value := fieldValue(session, "custom_api_url"); value != "" {
|
if value := fieldValue(session, "custom_api_url"); value != "" {
|
||||||
payload["custom_api_url"] = value
|
payload["custom_api_url"] = value
|
||||||
@@ -749,6 +749,59 @@ func (a *Agent) executeModelManagementAction(storeUserID string, userID int64, l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeModelName maps common user-friendly model aliases to the canonical
|
||||||
|
// names used by claw402 and other providers (e.g. "claude opus4.6" → "claude-opus").
|
||||||
|
func normalizeModelName(name string) string {
|
||||||
|
lower := strings.ToLower(strings.TrimSpace(name))
|
||||||
|
aliases := map[string]string{
|
||||||
|
// Claude
|
||||||
|
"claude opus": "claude-opus",
|
||||||
|
"claude opus4.6": "claude-opus",
|
||||||
|
"claude opus 4.6": "claude-opus",
|
||||||
|
"claude-opus-4-6": "claude-opus",
|
||||||
|
"claude sonnet": "claude-sonnet",
|
||||||
|
"claude sonnet4.6": "claude-sonnet",
|
||||||
|
"claude sonnet 4.6": "claude-sonnet",
|
||||||
|
"claude haiku": "claude-haiku",
|
||||||
|
// GPT
|
||||||
|
"gpt5.4": "gpt-5.4",
|
||||||
|
"gpt 5.4": "gpt-5.4",
|
||||||
|
"gpt5.4pro": "gpt-5.4-pro",
|
||||||
|
"gpt 5.4pro": "gpt-5.4-pro",
|
||||||
|
"gpt 5.4 pro": "gpt-5.4-pro",
|
||||||
|
"gpt5 mini": "gpt-5-mini",
|
||||||
|
"gpt 5 mini": "gpt-5-mini",
|
||||||
|
"gpt5.3": "gpt-5.3",
|
||||||
|
"gpt 5.3": "gpt-5.3",
|
||||||
|
// DeepSeek
|
||||||
|
"deepseek reasoner": "deepseek-reasoner",
|
||||||
|
"deepseek chat": "deepseek-chat",
|
||||||
|
// Qwen (通义千问)
|
||||||
|
"qwen max": "qwen-max",
|
||||||
|
"qwen plus": "qwen-plus",
|
||||||
|
"qwen turbo": "qwen-turbo",
|
||||||
|
"qwen flash": "qwen-flash",
|
||||||
|
"通义千问": "qwen-max",
|
||||||
|
// Gemini
|
||||||
|
"gemini 3.1 pro": "gemini-3.1-pro",
|
||||||
|
"gemini 3.1pro": "gemini-3.1-pro",
|
||||||
|
// Kimi
|
||||||
|
"kimi k2.5": "kimi-k2.5",
|
||||||
|
// GLM (智谱清言)
|
||||||
|
"glm5": "glm-5",
|
||||||
|
"glm 5": "glm-5",
|
||||||
|
"glm5 turbo": "glm-5-turbo",
|
||||||
|
"glm 5 turbo": "glm-5-turbo",
|
||||||
|
"glm5-turbo": "glm-5-turbo",
|
||||||
|
"智谱清言": "glm-5",
|
||||||
|
}
|
||||||
|
if canonical, ok := aliases[lower]; ok {
|
||||||
|
return canonical
|
||||||
|
}
|
||||||
|
// Replace spaces with hyphens as a general fallback
|
||||||
|
return strings.ReplaceAll(strings.TrimSpace(name), " ", "-")
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) executeStrategyManagementAction(storeUserID string, userID int64, lang, text string, session skillSession) string {
|
func (a *Agent) executeStrategyManagementAction(storeUserID string, userID int64, lang, text string, session skillSession) string {
|
||||||
switch session.Action {
|
switch session.Action {
|
||||||
case "query", "query_list":
|
case "query", "query_list":
|
||||||
|
|||||||
+18
-2
@@ -225,18 +225,34 @@ func (c *Claw402Client) signPayment(paymentHeaderB64 string) (string, error) {
|
|||||||
|
|
||||||
// ── Format overrides for Anthropic endpoints ─────────────────────────────────
|
// ── Format overrides for Anthropic endpoints ─────────────────────────────────
|
||||||
|
|
||||||
|
// stripMaxTokens removes per-call max_tokens caps from a body destined for
|
||||||
|
// claw402. The gateway already enforces a per-route default/floor/cap
|
||||||
|
// (see providers/*.yaml token_default_max_out / token_min_max_out /
|
||||||
|
// token_max_out_cap). Sending a small max_tokens here on a thinking model
|
||||||
|
// (Kimi K2.5, DeepSeek R1/V4) caused reasoning tokens to consume the entire
|
||||||
|
// budget and left `delta.content` empty, surfacing as "no content received".
|
||||||
|
// upto settles on real usage, so removing the cap costs nothing extra.
|
||||||
|
func stripMaxTokens(body map[string]any) map[string]any {
|
||||||
|
if body == nil {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
delete(body, "max_tokens")
|
||||||
|
delete(body, "max_completion_tokens")
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Claw402Client) BuildMCPRequestBody(systemPrompt, userPrompt string) map[string]any {
|
func (c *Claw402Client) BuildMCPRequestBody(systemPrompt, userPrompt string) map[string]any {
|
||||||
if c.claudeProxy != nil {
|
if c.claudeProxy != nil {
|
||||||
return c.claudeProxy.BuildMCPRequestBody(systemPrompt, userPrompt)
|
return c.claudeProxy.BuildMCPRequestBody(systemPrompt, userPrompt)
|
||||||
}
|
}
|
||||||
return c.Client.BuildMCPRequestBody(systemPrompt, userPrompt)
|
return stripMaxTokens(c.Client.BuildMCPRequestBody(systemPrompt, userPrompt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Claw402Client) BuildRequestBodyFromRequest(req *mcp.Request) map[string]any {
|
func (c *Claw402Client) BuildRequestBodyFromRequest(req *mcp.Request) map[string]any {
|
||||||
if c.claudeProxy != nil {
|
if c.claudeProxy != nil {
|
||||||
return c.claudeProxy.BuildRequestBodyFromRequest(req)
|
return c.claudeProxy.BuildRequestBodyFromRequest(req)
|
||||||
}
|
}
|
||||||
return c.Client.BuildRequestBodyFromRequest(req)
|
return stripMaxTokens(c.Client.BuildRequestBodyFromRequest(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Claw402Client) ParseMCPResponse(body []byte) (string, error) {
|
func (c *Claw402Client) ParseMCPResponse(body []byte) (string, error) {
|
||||||
|
|||||||
@@ -108,12 +108,16 @@ export default function HeaderBar({
|
|||||||
path: string
|
path: string
|
||||||
label: string
|
label: string
|
||||||
requiresAuth: boolean
|
requiresAuth: boolean
|
||||||
|
badge?: string
|
||||||
|
hidden?: boolean
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
page: 'agent',
|
page: 'agent',
|
||||||
path: ROUTES.agent,
|
path: ROUTES.agent,
|
||||||
label: 'Agent',
|
label: 'Agent',
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
|
badge: 'Beta',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
page: 'data',
|
page: 'data',
|
||||||
@@ -182,7 +186,7 @@ export default function HeaderBar({
|
|||||||
navigateInApp(tab.path)
|
navigateInApp(tab.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return navTabs.map((tab) => (
|
return navTabs.filter((tab) => !tab.hidden).map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.page}
|
key={tab.page}
|
||||||
onClick={() => handleNavClick(tab)}
|
onClick={() => handleNavClick(tab)}
|
||||||
@@ -193,6 +197,11 @@ export default function HeaderBar({
|
|||||||
<span className="absolute inset-0 rounded-lg bg-nofx-gold/15 -z-10" />
|
<span className="absolute inset-0 rounded-lg bg-nofx-gold/15 -z-10" />
|
||||||
)}
|
)}
|
||||||
{tab.label}
|
{tab.label}
|
||||||
|
{tab.badge && (
|
||||||
|
<span className="ml-1 text-[10px] px-1.5 py-0.5 rounded-full bg-nofx-gold/20 text-nofx-gold font-semibold uppercase align-top relative -top-1">
|
||||||
|
{tab.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
})()}
|
})()}
|
||||||
@@ -436,12 +445,16 @@ export default function HeaderBar({
|
|||||||
path: string
|
path: string
|
||||||
label: string
|
label: string
|
||||||
requiresAuth: boolean
|
requiresAuth: boolean
|
||||||
|
badge?: string
|
||||||
|
hidden?: boolean
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
page: 'agent',
|
page: 'agent',
|
||||||
path: ROUTES.agent,
|
path: ROUTES.agent,
|
||||||
label: 'Agent',
|
label: 'Agent',
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
|
badge: 'Beta',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
page: 'data',
|
page: 'data',
|
||||||
@@ -510,7 +523,7 @@ export default function HeaderBar({
|
|||||||
setMobileMenuOpen(false)
|
setMobileMenuOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return navTabs.map((tab, i) => (
|
return navTabs.filter((tab) => !tab.hidden).map((tab, i) => (
|
||||||
<motion.button
|
<motion.button
|
||||||
key={tab.page}
|
key={tab.page}
|
||||||
initial={{ x: -20, opacity: 0 }}
|
initial={{ x: -20, opacity: 0 }}
|
||||||
@@ -527,6 +540,11 @@ export default function HeaderBar({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{tab.label}
|
{tab.label}
|
||||||
|
{tab.badge && (
|
||||||
|
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-nofx-gold/20 text-nofx-gold font-semibold uppercase align-middle relative -top-1">
|
||||||
|
{tab.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{tab.requiresAuth && !isLoggedIn && (
|
{tab.requiresAuth && !isLoggedIn && (
|
||||||
<span className="text-[10px] px-1.5 py-0.5 rounded border border-zinc-800 text-zinc-500 font-normal tracking-wide uppercase align-middle relative -top-1">
|
<span className="text-[10px] px-1.5 py-0.5 rounded border border-zinc-800 text-zinc-500 font-normal tracking-wide uppercase align-middle relative -top-1">
|
||||||
LOGIN_REQ
|
LOGIN_REQ
|
||||||
|
|||||||
Reference in New Issue
Block a user