chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,135 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Agent Assistant</title>
<!-- Include custom styles -->
<link rel="stylesheet" href="/static/style.css" />
<!-- Google Material Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Outfit&family=Material+Symbols+Outlined"
rel="stylesheet"
/>
<!-- Import Google Material 3 Web Components -->
<!-- See https://m3.material.io/develop/web -->
<script type="module" src="https://esm.run/@material/web/all.js"></script>
</head>
<body>
<div class="app-container">
<!-- Sidebar for Settings (Material 3 styled form) -->
<div id="settings-sidebar" class="sidebar">
<div class="sidebar-header">
<h2>Configuration Settings</h2>
</div>
<div class="settings-form">
<!-- Remote Agent Settings -->
<div id="remote-settings">
<md-outlined-text-field
id="project-id"
label="Project ID"
placeholder="e.g. my-gcp-project"
required
></md-outlined-text-field>
<md-outlined-text-field
id="location"
label="Location"
placeholder="e.g. us-west1"
required
></md-outlined-text-field>
<md-filled-button id="load-agents-btn" class="btn-full">
<span class="material-symbols-outlined" slot="icon">sync</span>
Load Remote Agents
</md-filled-button>
<md-outlined-select id="agent-select" label="Select Engine">
<md-select-option value="">
<div slot="headline">Select an engine...</div>
</md-select-option>
</md-outlined-select>
<!-- Hidden hold for current target ID -->
<input type="hidden" id="agent-id" />
</div>
<!-- User ID for authentication tracking -->
<md-outlined-text-field
id="user-id"
label="User ID"
value="default_user_id"
required
></md-outlined-text-field>
<md-filled-button id="save-settings" class="btn-full">
<span class="material-symbols-outlined" slot="icon">save</span>
Save & Apply Settings
</md-filled-button>
<!-- Active Agent & Session Profile Pane -->
<div id="agent-info-pane" class="agent-info-pane">
<h4>Active Agent Profile</h4>
<div class="info-grid">
<div class="info-row">
<span class="info-label">Mode:</span>
<span class="info-value" id="info-agent-mode">Remote Vertex AI</span>
</div>
<div class="info-row">
<span class="info-label">Project ID:</span>
<span class="info-value" id="info-project-id">-</span>
</div>
<div class="info-row">
<span class="info-label">Location:</span>
<span class="info-value" id="info-location">-</span>
</div>
<div class="info-row">
<span class="info-label">Target ID:</span>
<span class="info-value" id="info-agent-id">-</span>
</div>
<div class="info-row">
<span class="info-label">Session ID:</span>
<span class="info-value" id="info-session-id">No active session</span>
</div>
<div class="info-row">
<span class="info-label">User ID:</span>
<span class="info-value" id="info-user-id">default_user_id</span>
</div>
</div>
</div>
</div>
</div>
<!-- Main Chat Area -->
<div class="chat-container">
<!-- Header with title and clear chat button -->
<header class="chat-header">
<div class="header-left">
<div class="agent-info">
<h1>Agent Playground</h1>
</div>
</div>
<div class="header-right"></div>
</header>
<!-- Message History Container -->
<div id="messages-container" class="messages-container"></div>
<!-- Input Area -->
<footer class="input-container">
<input
type="text"
id="user-input"
placeholder="Type a message to your agent..."
autofocus
autocomplete="off"
/>
<md-filled-icon-button id="send-btn" disabled>
<span class="material-symbols-outlined">send</span>
</md-filled-icon-button>
</footer>
</div>
</div>
<!-- jQuery Library -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Local JavaScript Hook -->
<script src="/static/script.js"></script>
</body>
</html>
@@ -0,0 +1,407 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
$(function() {
const $messagesContainer = $('#messages-container');
const $userInput = $('#user-input');
const $sendBtn = $('#send-btn');
let currentSessionId = null;
/**
* Updates the active agent profile information panel in the sidebar
* based on the currently selected remote agent and user ID configurations.
*/
const updateAgentInfoPane = () => {
const projectId = $('#project-id').val();
const location = $('#location').val();
const agentId = $('#agent-id').val() || $('#agent-select').val();
const userId = $('#user-id').val() || 'default_user_id';
$('#info-agent-mode').text('Remote Vertex AI');
$('#info-project-id').text(projectId || '-');
$('#info-location').text(location || '-');
$('#info-agent-id').text(agentId || '-');
$('#info-session-id').text(currentSessionId || 'No active session');
$('#info-user-id').text(userId || 'default_user_id');
};
/**
* Resets the message history container to display the initial greeting
* and instructions for the user.
*/
const resetChatFeed = () => {
$messagesContainer.html(`
<div class="message system-message">
<div class="message-content">
Hi! I am your AI Assistant. Configure your target remote agent in the panel on the left, and type a query below to load the sandbox stream.
</div>
</div>
`);
};
/**
* Asynchronously fetches the list of available remote agents from the backend
* API and populates the remote agent selection dropdown.
*/
function loadRemoteAgents(showAlert = false) {
const projectId = $('#project-id').val().trim();
const location = $('#location').val().trim();
updateAgentInfoPane();
if (!projectId || !location) {
if (showAlert) alert('Please configure both Project ID and Location first.');
return;
}
const $loadAgentsBtn = $('#load-agents-btn');
const $agentSelect = $('#agent-select');
$loadAgentsBtn.prop('disabled', true).html('<span class="material-symbols-outlined icon-spin" slot="icon">sync</span> Loading...');
$.getJSON('/list_agents', { project_id: projectId, location: location })
.done(data => {
if (data.error) {
if (showAlert) alert(`GCP Error: ${data.error}`);
console.error(`Remote agent fetch error: ${data.error}`);
} else if (data.agents) {
$agentSelect.html('<md-select-option value=""><div slot="headline">Select an engine...</div></md-select-option>');
data.agents.forEach(agent => {
$agentSelect.append(
$('<md-select-option>').val(agent.id).append(
$('<div>').attr('slot', 'headline').text(`${agent.name} (${agent.id})`)
)
);
});
const currentAgentId = $('#agent-id').val();
if (currentAgentId) {
$agentSelect.val(currentAgentId);
}
updateAgentInfoPane();
}
})
.fail((jqXHR, textStatus, errorThrown) => {
console.error('Failed to load remote agents:', errorThrown);
if (showAlert) alert('Failed to communicate with GCP reasoning engines.');
})
.always(() => {
$loadAgentsBtn.prop('disabled', false).html('<span class="material-symbols-outlined" slot="icon">sync</span> Load Remote Agents');
});
}
$('#agent-select').on('change', function() {
const selectedId = $(this).val();
$('#agent-id').val(selectedId);
currentSessionId = null;
resetChatFeed();
updateAgentInfoPane();
});
/**
* Initializes default configuration values on fresh page loads
* and updates the active agent profile panel accordingly.
*/
const loadSettings = () => {
const projectId = '';
const location = '';
const agentId = '';
const userId = 'default_user_id';
$('#project-id').val(projectId);
$('#location').val(location);
$('#agent-id').val(agentId);
$('#user-id').val(userId);
updateAgentInfoPane();
};
// Apply configs to active session
$('#save-settings').on('click', () => {
const selectVal = $('#agent-select').val();
if (selectVal) {
$('#agent-id').val(selectVal);
}
currentSessionId = null;
document.cookie =
'session_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; samesite=lax';
resetChatFeed();
updateAgentInfoPane();
alert('Settings applied and session reset successfully!');
});
$('#load-agents-btn').on('click', () => loadRemoteAgents(true));
// Initialize
loadSettings();
// Handle send button states on user inputs
$userInput.on('input', function() {
$sendBtn.prop('disabled', $(this).val().trim() === '');
});
$userInput.on('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
sendMessage();
}
});
$sendBtn.on('click', sendMessage);
/**
* Handles the user message submission workflow, disabling input controls
* during processing, appending the user's message to the chat container, and
* initiating the backend streaming request.
*/
async function sendMessage() {
const text = $userInput.val().trim();
if (!text) return;
$userInput.val('');
$sendBtn.prop('disabled', true);
appendMessage(text, 'user-message');
await executeChatRequest(text, false, null, null, null);
}
/**
* Executes a POST request to the /chat API endpoint and processes the
* Server-Sent Events (SSE) stream. Handles agent messages, tool execution
* progress, and OAuth popup authentication resumes.
*
* @param {?string} text - The user query or prompt to send to the agent.
* @param {?boolean} isAuthResume - Indicates whether the request is resuming
* from an OAuth popup authentication flow.
* @param {?string|null} authRequestId - The function call ID associated with
* the credentials request.
* @param {?object|null} authConfig - The authentication configuration
* parameters returned by the agent tool.
* @param {?HTMLElement|null=} existingAgentMessageDiv - An existing message
* container element to append streaming responses into.
*/
async function executeChatRequest(
text, isAuthResume, authRequestId, authConfig,
existingAgentMessageDiv = null) {
let $agentMessageDiv;
let $contentDiv;
let isFirstEvent = true;
if (existingAgentMessageDiv) {
$agentMessageDiv = $(existingAgentMessageDiv);
$contentDiv = $agentMessageDiv.find('.message-content');
isFirstEvent = false;
} else {
$agentMessageDiv = appendMessage(
'<div class="agent-loader"><span class="material-symbols-outlined icon-spin">sync</span> Thinking...</div>',
'agent-message');
$contentDiv = $agentMessageDiv.find('.message-content');
}
const agentType = 'remote';
const localAgent = '';
const projectId = $('#project-id').val();
const location = $('#location').val();
const agentId = $('#agent-id').val() || $('#agent-select').val();
const userId = $('#user-id').val();
const formatAgentText = (inputVal) => {
if (typeof inputVal !== 'string') return inputVal;
return inputVal.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/\n/g, '<br>');
};
try {
const requestBody = {
message: text || '',
agent_type: agentType,
local_agent: localAgent,
project_id: projectId,
location: location,
agent_id: agentId,
user_id: userId,
is_auth_resume: isAuthResume,
auth_request_function_call_id: authRequestId,
auth_config: authConfig
};
if (currentSessionId) {
requestBody.session_id = currentSessionId;
}
const response = await fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
let errorDetail = '';
try {
const errData = await response.json();
errorDetail = errData.detail ? JSON.stringify(errData.detail) :
JSON.stringify(errData);
} catch (e) {
try {
errorDetail = await response.text();
} catch (t) {
errorDetail = `Status ${response.status}`;
}
}
throw new Error(`HTTP connection error (Status ${response.status}): ${
errorDetail}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
while (true) {
const {value, done} = await reader.read();
if (done) break;
buffer += decoder.decode(value, {stream: true});
while (true) {
const eventEnd = buffer.indexOf('\n\n');
if (eventEnd === -1) break;
const event = buffer.substring(0, eventEnd);
buffer = buffer.substring(eventEnd + 2);
if (event.startsWith('data: ')) {
const dataStr = event.substring(6);
try {
const data = JSON.parse(dataStr);
if (data.session_id) {
currentSessionId = data.session_id;
document.cookie =
`session_id=${currentSessionId}; path=/; samesite=lax`;
updateAgentInfoPane();
continue;
}
if (isFirstEvent) {
$contentDiv.empty();
isFirstEvent = false;
}
if (data.popup_auth_uri) {
if (data.consent_nonce) {
document.cookie = `consent_nonce=${
data.consent_nonce}; path=/; samesite=lax`;
}
const currentUserId = $('#user-id').val();
document.cookie =
`consent_user_id=${currentUserId}; path=/; samesite=lax`;
const popup = window.open(data.popup_auth_uri, '_blank');
if (popup) {
const timer = setInterval(() => {
if (popup.closed) {
clearInterval(timer);
$contentDiv.append(
'<br><em>Authentication complete. Resuming session...</em><br>');
$messagesContainer.scrollTop(
$messagesContainer.prop('scrollHeight'));
executeChatRequest(
'', true, data.auth_request_function_call_id,
data.auth_config, $agentMessageDiv[0]);
}
}, 500);
}
$contentDiv.append(
`<span>Please log in to complete authorization in the popup. <a href="${
data.popup_auth_uri}" target="_blank">Open login window manually.</a></span>`);
}
const errorMsg = data.error || data.error_message ||
data.errorMessage || data.error_code || data.errorCode;
if (errorMsg) {
const $err = $('<div>')
.addClass('error-header')
.text(`Error: ${errorMsg}`);
$contentDiv.append($err);
if (data.traceback) {
const $pre = $('<pre>')
.addClass('error-traceback')
.text(data.traceback);
$contentDiv.append($pre);
}
$agentMessageDiv.addClass('error-message');
} else if (data.content && data.content.parts) {
data.content.parts.forEach(part => {
if (part.text) {
$contentDiv.append(
$('<div>').html(formatAgentText(part.text)));
}
});
} else if (data.text) {
$contentDiv.append($('<div>').html(formatAgentText(data.text)));
} else if (typeof data === 'string') {
$contentDiv.append($('<div>').html(formatAgentText(data)));
}
$messagesContainer.scrollTop(
$messagesContainer.prop('scrollHeight'));
} catch (err) {
console.error('Error parsing JSON event chunk:', dataStr, err);
}
}
}
}
} catch (error) {
console.error('Error during query stream processing:', error);
if (isFirstEvent) {
$contentDiv.empty();
}
$contentDiv.append(
$('<div>')
.addClass('error-header')
.text(`Network / connection error: ${error.message}`));
$agentMessageDiv.addClass('error-message');
$messagesContainer.scrollTop($messagesContainer.prop('scrollHeight'));
}
}
/**
* Helper utility to create and append a new message container element (user,
* agent, or system) to the chat history DOM, automatically scrolling the view
* to the latest message.
*
* @param {string} text - The HTML or plaintext content of the message.
* @param {string} type - The CSS class defining the message type (e.g.,
* 'user-message', 'agent-message').
* @returns {!jQuery} The jQuery wrapper representing the newly created message
* element.
*/
function appendMessage(text, type) {
const $messageDiv = $('<div>').addClass(`message ${type}`);
const $contentDiv = $('<div>')
.addClass('message-content')
.html(text ? text.replace(/\n/g, '<br>') : '');
$messageDiv.append($contentDiv);
$messagesContainer.append($messageDiv);
$messagesContainer.scrollTop($messagesContainer.prop('scrollHeight'));
return $messageDiv;
}
});
@@ -0,0 +1,291 @@
/* ==========================================================================
Root Variables & Theming
Defines the core Google Material 3 color palette, backgrounds, and borders.
========================================================================== */
:root {
--bg-color: #f8f9fa;
--text-color: #1f1f1f;
--text-muted: #5f6368;
--sidebar-bg: #f0f4f9;
--chat-bg: #ffffff;
--message-user-bg: #d3e3fd;
--message-user-text: #041e49;
--message-agent-bg: #f1f3f4;
--message-agent-text: #1f1f1f;
--border-color: #dadce0;
}
/* ==========================================================================
Global Resets & Base Layout
Establishes box-sizing, typography, and the full-bleed application container.
========================================================================== */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Google Sans', 'Segoe UI', Roboto, sans-serif;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
overflow: hidden;
height: 100vh;
}
.app-container {
display: flex;
width: 100vw;
height: 100vh;
}
/* ==========================================================================
Sidebar Configuration Panel
Styles the pinned left navigation drawer housing agent and user settings.
========================================================================== */
.sidebar {
width: 320px;
background-color: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
}
.sidebar-header {
padding: 18px 24px;
border-bottom: 1px solid var(--border-color);
}
.sidebar-header h2 {
font-size: 1.15rem;
font-weight: 500;
}
.settings-form {
padding: 24px;
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
#remote-settings {
display: flex;
flex-direction: column;
gap: 16px;
}
.btn-full {
width: 100%;
}
/* ==========================================================================
Main Chat Playground Area
Configures the primary chat interface, header, and dynamic message history feed.
========================================================================== */
.chat-container {
flex-grow: 1;
display: flex;
flex-direction: column;
background-color: var(--chat-bg);
height: 100%;
min-width: 0;
}
.chat-header {
padding: 14px 24px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--chat-bg);
}
.agent-info h1 {
font-size: 1.15rem;
font-weight: 500;
}
.messages-container {
flex-grow: 1;
overflow-y: auto;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* ==========================================================================
Message Bubbles & Formatting
Provides distinct visual styling for user, agent, system, and error bubbles.
========================================================================== */
.message {
max-width: 75%;
padding: 12px 18px;
border-radius: 18px;
line-height: 1.55;
word-wrap: break-word;
font-size: 0.92rem;
}
.system-message {
align-self: center;
background-color: var(--bg-color);
border: 1px solid var(--border-color);
color: var(--text-muted);
text-align: center;
max-width: 85%;
border-radius: 12px;
}
.user-message {
align-self: flex-end;
background-color: var(--message-user-bg);
color: var(--message-user-text);
border-bottom-right-radius: 4px;
}
.agent-message {
align-self: flex-start;
background-color: var(--message-agent-bg);
color: var(--message-agent-text);
border-bottom-left-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.04);
}
.error-message {
align-self: center;
background-color: #fce8e6;
color: #c5221f;
border: 1px solid rgba(197, 34, 31, 0.25);
border-radius: 12px;
}
.error-header {
font-weight: 600;
}
.error-traceback {
margin-top: 8px;
background-color: #fce8e6;
border: 1px solid rgba(197, 34, 31, 0.2);
color: #c5221f;
padding: 10px;
border-radius: 8px;
font-size: 0.8rem;
overflow-x: auto;
font-family: Consolas, Courier, monospace;
}
/* ==========================================================================
Input Bar & Actions
Designs the bottom prompt bar and send button.
========================================================================== */
.input-container {
padding: 18px 24px;
border-top: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 16px;
background-color: var(--chat-bg);
}
.input-container input {
flex-grow: 1;
padding: 14px 18px;
border-radius: 24px;
background-color: #f1f3f4;
border: 1px solid #transparent;
color: var(--text-color);
font-size: 0.95rem;
outline: none;
transition: background-color 150ms ease, border-color 150ms ease;
}
.input-container input:focus {
background-color: var(--chat-bg);
border-color: var(--border-color);
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.15);
}
/* Material symbols loading spins */
.agent-loader {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-muted);
}
.icon-spin {
display: inline-block;
animation: spin 2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Active Agent Profile configurations */
.agent-info-pane {
margin-top: 20px;
padding: 16px;
border-radius: 12px;
background-color: var(--chat-bg);
border: 1px solid var(--border-color);
}
.agent-info-pane h4 {
font-size: 0.75rem;
font-weight: 500;
color: var(--text-muted);
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.info-grid {
display: flex;
flex-direction: column;
gap: 8px;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
gap: 8px;
}
.info-label {
color: var(--text-muted);
font-weight: 500;
}
.info-value {
color: var(--text-color);
font-weight: 600;
word-break: break-all;
}
/* Custom scrollbars */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(60, 64, 67, 0.2);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(60, 64, 67, 0.35);
}