From 1f12ff6ca0a8e3252ce16f952ab17193747a7e05 Mon Sep 17 00:00:00 2001 From: Dhanji Prasanna Date: Fri, 7 Nov 2025 09:50:43 +1100 Subject: [PATCH] fix refresh and max_tokens bug --- config.example.toml | 8 +- crates/g3-config/src/lib.rs | 3 + crates/g3-console/web/js/router.js | 200 ++++++++++++++++++++++++--- crates/g3-console/web/styles/app.css | 1 + crates/g3-core/src/lib.rs | 12 +- 5 files changed, 200 insertions(+), 24 deletions(-) diff --git a/config.example.toml b/config.example.toml index 832f333..330ff7e 100644 --- a/config.example.toml +++ b/config.example.toml @@ -10,12 +10,18 @@ default_provider = "databricks" host = "https://your-workspace.cloud.databricks.com" # token = "your-databricks-token" # Optional - will use OAuth if not provided model = "databricks-claude-sonnet-4" -max_tokens = 4096 +max_tokens = 4096 # Per-request output limit (how many tokens the model can generate per response) + # Note: This is different from max_context_length (total conversation history size) temperature = 0.1 use_oauth = true [agent] fallback_default_max_tokens = 8192 +# max_context_length: Override the context window size for all providers +# This is the total size of conversation history, not per-request output limit +# Useful for models with large context windows (e.g., Claude with 200k tokens) +# If not set, uses provider-specific defaults based on model capabilities +# max_context_length = 200000 enable_streaming = true timeout_seconds = 60 # Retry configuration for recoverable errors (timeouts, rate limits, etc.) diff --git a/crates/g3-config/src/lib.rs b/crates/g3-config/src/lib.rs index 87a4e2a..189a1ee 100644 --- a/crates/g3-config/src/lib.rs +++ b/crates/g3-config/src/lib.rs @@ -62,6 +62,7 @@ pub struct EmbeddedConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentConfig { + pub max_context_length: Option, pub fallback_default_max_tokens: usize, pub enable_streaming: bool, pub timeout_seconds: u64, @@ -135,6 +136,7 @@ impl Default for Config { player: None, // Will use default_provider if not specified }, agent: AgentConfig { + max_context_length: None, fallback_default_max_tokens: 8192, enable_streaming: true, timeout_seconds: 60, @@ -253,6 +255,7 @@ impl Config { player: None, // Will use default_provider if not specified }, agent: AgentConfig { + max_context_length: None, fallback_default_max_tokens: 8192, enable_streaming: true, timeout_seconds: 60, diff --git a/crates/g3-console/web/js/router.js b/crates/g3-console/web/js/router.js index c026db3..8edb69b 100644 --- a/crates/g3-console/web/js/router.js +++ b/crates/g3-console/web/js/router.js @@ -97,6 +97,15 @@ const router = { return; } + // Check if we already have a container for instances + let instancesList = container.querySelector('.instances-list'); + const isInitialLoad = !instancesList; + + if (isInitialLoad) { + instancesList = document.createElement('div'); + instancesList.className = 'instances-list'; + } + if (instances.length === 0) { console.log('[Router] No instances, showing empty state'); container.innerHTML = components.emptyState( @@ -104,15 +113,51 @@ const router = { ); } else { console.log('[Router] Building HTML for', instances.length, 'instances'); - let html = '
'; - for (const instance of instances) { - const stats = instance.stats || { total_tokens: 0, tool_calls: 0, errors: 0, duration_secs: 0 }; - html += components.instancePanel(instance, stats, instance.latest_message); - } - html += '
'; - console.log('[Router] Setting innerHTML (', html.length, 'chars)'); - container.innerHTML = html; + // Build a map of existing panels for efficient lookup + const existingPanels = new Map(); + if (!isInitialLoad) { + instancesList.querySelectorAll('.instance-panel').forEach(panel => { + const id = panel.getAttribute('data-id'); + if (id) existingPanels.set(id, panel); + }); + } + + // Track which IDs we've seen + const currentIds = new Set(); + + for (const instance of instances) { + currentIds.add(instance.id); + const stats = instance.stats || { total_tokens: 0, tool_calls: 0, errors: 0, duration_secs: 0 }; + const newHtml = components.instancePanel(instance, stats, instance.latest_message); + + const existingPanel = existingPanels.get(instance.id); + if (existingPanel) { + // Update existing panel in-place + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = newHtml; + const newPanel = tempDiv.firstElementChild; + existingPanel.replaceWith(newPanel); + } else { + // Add new panel + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = newHtml; + instancesList.appendChild(tempDiv.firstElementChild); + } + } + + // Remove panels for instances that no longer exist + existingPanels.forEach((panel, id) => { + if (!currentIds.has(id)) { + panel.remove(); + } + }); + + if (isInitialLoad) { + container.innerHTML = ''; + container.appendChild(instancesList); + } + console.log('[Router] HTML set successfully'); } @@ -137,7 +182,14 @@ const router = { console.log('[Router] renderDetail called for', id); this.currentInstanceId = id; - container.innerHTML = components.spinner('Loading instance details...'); + + // Check if we already have a detail view for this instance + let detailView = container.querySelector('.detail-view'); + const isInitialLoad = !detailView || detailView.getAttribute('data-instance-id') !== id; + + if (isInitialLoad) { + container.innerHTML = components.spinner('Loading instance details...'); + } try { const instance = await api.getInstance(id); @@ -149,9 +201,24 @@ const router = { return; } + // If not initial load, update in place + if (!isInitialLoad) { + detailView = container.querySelector('.detail-view'); + if (detailView) { + this.updateDetailView(detailView, instance, logs); + // Schedule next refresh + if (this.currentRoute === `/instance/${id}`) { + this.detailRefreshTimeout = setTimeout(() => { + this.renderDetail(container, id); + }, 3000); + } + return; + } + } + // Build detail view HTML let html = ` -
+

${instance.workspace}

@@ -159,19 +226,19 @@ const router = {
-
+
Tokens
${(instance.stats?.total_tokens || 0).toLocaleString()}
-
+
Tool Calls
${instance.stats?.tool_calls || 0}
-
+
Errors
${instance.stats?.errors || 0}
-
+
Duration
${Math.round((instance.stats?.duration_secs || 0) / 60)}m
@@ -179,17 +246,17 @@ const router = {

Git Status

- ${components.gitStatus(instance.git_status)} +
${components.gitStatus(instance.git_status)}

Project Files

- ${components.projectFiles(instance.project_files)} +
${components.projectFiles(instance.project_files)}

Tool Calls

-
+
`; // Render tool calls @@ -241,6 +308,105 @@ const router = { console.error('[Router] Error in renderDetail:', error); container.innerHTML = components.error('Failed to load instance: ' + error.message); } + }, + + updateDetailView(detailView, instance, logs) { + // Update status badge + const statusBadge = detailView.querySelector('.detail-header .badge'); + if (statusBadge) { + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = components.statusBadge(instance.status); + statusBadge.replaceWith(tempDiv.firstElementChild); + } + + // Update stats + const tokensStat = detailView.querySelector('[data-stat="tokens"] .stat-value'); + if (tokensStat) { + tokensStat.textContent = (instance.stats?.total_tokens || 0).toLocaleString(); + } + + const toolCallsStat = detailView.querySelector('[data-stat="tool_calls"] .stat-value'); + if (toolCallsStat) { + toolCallsStat.textContent = instance.stats?.tool_calls || 0; + } + + const errorsStat = detailView.querySelector('[data-stat="errors"] .stat-value'); + if (errorsStat) { + errorsStat.textContent = instance.stats?.errors || 0; + } + + const durationStat = detailView.querySelector('[data-stat="duration"] .stat-value'); + if (durationStat) { + durationStat.textContent = Math.round((instance.stats?.duration_secs || 0) / 60) + 'm'; + } + + // Update git status + const gitStatusContainer = detailView.querySelector('.git-status-container'); + if (gitStatusContainer) { + gitStatusContainer.innerHTML = components.gitStatus(instance.git_status); + } + + // Update project files + const projectFilesContainer = detailView.querySelector('.project-files-container'); + if (projectFilesContainer) { + projectFilesContainer.innerHTML = components.projectFiles(instance.project_files); + } + + // Update tool calls + const toolCallsSection = detailView.querySelector('[data-section="tool-calls"]'); + if (toolCallsSection && logs && logs.tool_calls) { + // Build a map of existing tool calls + const existingToolCalls = new Map(); + toolCallsSection.querySelectorAll('.tool-call').forEach(tc => { + const id = tc.getAttribute('data-tool-id'); + if (id) existingToolCalls.set(id, tc); + }); + + // Track which IDs we've seen + const currentIds = new Set(); + + if (logs.tool_calls.length > 0) { + for (const toolCall of logs.tool_calls) { + currentIds.add(toolCall.id); + const newHtml = components.toolCall(toolCall); + + const existingToolCall = existingToolCalls.get(toolCall.id); + if (existingToolCall) { + // Update existing tool call in-place + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = newHtml; + existingToolCall.replaceWith(tempDiv.firstElementChild); + } else { + // Add new tool call + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = newHtml; + toolCallsSection.appendChild(tempDiv.firstElementChild); + } + } + + // Remove tool calls that no longer exist + existingToolCalls.forEach((tc, id) => { + if (!currentIds.has(id)) { + tc.remove(); + } + }); + } + } + + // Update chat messages + const chatMessages = detailView.querySelector('.chat-messages'); + if (chatMessages && logs && logs.messages && logs.messages.length > 0) { + let html = ''; + for (const msg of logs.messages) { + html += components.chatMessage(msg.content, msg.agent); + } + chatMessages.innerHTML = html; + } + + // Re-apply syntax highlighting to any new code blocks + detailView.querySelectorAll('pre code:not(.hljs)').forEach((block) => { + hljs.highlightElement(block); + } } }; diff --git a/crates/g3-console/web/styles/app.css b/crates/g3-console/web/styles/app.css index e2a162d..1d8ce73 100644 --- a/crates/g3-console/web/styles/app.css +++ b/crates/g3-console/web/styles/app.css @@ -39,6 +39,7 @@ body { background-color: var(--bg-secondary); color: var(--text-primary); line-height: 1.6; + font-size: 75%; } /* Header */ diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index ee3f3e1..a3d248f 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -992,6 +992,12 @@ impl Agent { } fn get_configured_context_length(config: &Config, providers: &ProviderRegistry) -> Result { + // First, check if there's a global max_context_length override in agent config + if let Some(max_context_length) = config.agent.max_context_length { + debug!("Using configured agent.max_context_length: {}", max_context_length); + return Ok(max_context_length); + } + // Get the configured max_tokens for the current provider fn get_provider_max_tokens(config: &Config, provider_name: &str) -> Option { match provider_name { @@ -1008,12 +1014,6 @@ impl Agent { let provider_name = provider.name(); let model_name = provider.model(); - // Check if there's a configured context length override first - if let Some(max_tokens) = get_provider_max_tokens(config, provider_name) { - debug!("Using configured max_tokens for {}: {}", provider_name, max_tokens); - return Ok(max_tokens); - } - // Use provider-specific context length if available, otherwise fall back to agent config let context_length = match provider_name { "embedded" => {