Merge pull request #9909 from continuedev/nate/fixes-4888

Add detailed logging for metadata endpoint requests
This commit is contained in:
Nate Sesti
2026-01-25 23:30:03 -08:00
committed by GitHub
3 changed files with 77 additions and 56 deletions

View File

@@ -1,10 +1,35 @@
import type { Session, ToolStatus } from "core/index.js";
import type { ChatHistoryItem, Session, ToolStatus } from "core/index.js";
import { services } from "../services/index.js";
import { streamChatResponse } from "../stream/streamChatResponse.js";
import { StreamCallbacks } from "../stream/streamChatResponse.types.js";
import { logger } from "../util/logger.js";
/**
* Remove partial assistant message if the last message is an empty assistant message.
* Used when a response is interrupted.
*/
export function removePartialAssistantMessage(
sessionHistory: ChatHistoryItem[],
): void {
try {
const svcHistory = services.chatHistory.getHistory();
const last = svcHistory[svcHistory.length - 1];
if (last && last.message.role === "assistant" && !last.message.content) {
services.chatHistory.setHistory(svcHistory.slice(0, -1));
}
} catch {
const lastMessage = sessionHistory[sessionHistory.length - 1];
if (
lastMessage &&
lastMessage.message.role === "assistant" &&
!lastMessage.message.content
) {
sessionHistory.pop();
}
}
}
// Modified version of streamChatResponse that supports interruption
export async function streamChatResponseWithInterruption(
state: ServerState,
@@ -124,3 +149,21 @@ export interface ServerState {
pendingPermission: PendingPermission | null;
systemMessage?: string;
}
/**
* Check if the agent should be marked as complete based on conversation history.
* The agent is complete if the last message is from the assistant and has no tool calls.
*/
export function checkAgentComplete(
history: { message: { role: string; tool_calls?: any[] } }[] | undefined,
): boolean {
if (!history || history.length === 0) {
return false;
}
const lastItem = history[history.length - 1];
if (lastItem?.message?.role !== "assistant") {
return false;
}
const toolCalls = (lastItem.message as any).tool_calls;
return !toolCalls || toolCalls.length === 0;
}

View File

@@ -40,6 +40,8 @@ import { readStdinSync } from "../util/stdin.js";
import { ExtendedCommandOptions } from "./BaseCommandOptions.js";
import {
checkAgentComplete,
removePartialAssistantMessage,
streamChatResponseWithInterruption,
type ServerState,
} from "./serve.helpers.js";
@@ -464,28 +466,6 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
}
});
// Process messages from the queue
function removePartialAssistantMessage(state: ServerState) {
try {
const svcHistory = services.chatHistory.getHistory();
const last = svcHistory[svcHistory.length - 1];
if (last && last.message.role === "assistant" && !last.message.content) {
const trimmed = svcHistory.slice(0, -1);
services.chatHistory.setHistory(trimmed);
}
} catch {
const lastMessage =
state.session.history[state.session.history.length - 1];
if (
lastMessage &&
lastMessage.message.role === "assistant" &&
!lastMessage.message.content
) {
state.session.history.pop();
}
}
}
async function processMessages(state: ServerState, llmApi: any) {
let processedMessage = false;
while (state.serverRunning) {
@@ -529,21 +509,11 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
// Update metadata after successful agent turn
try {
const history = services.chatHistory?.getHistory();
// Check if the agent should be marked as complete
// The agent is complete if the last message is from the assistant and has no tool calls
let isComplete = false;
if (history && history.length > 0) {
const lastItem = history[history.length - 1];
if (lastItem?.message?.role === "assistant") {
const toolCalls = (lastItem.message as any).tool_calls;
isComplete = !toolCalls || toolCalls.length === 0;
}
}
await updateAgentMetadata({ history, isComplete });
await updateAgentMetadata({
history,
isComplete: checkAgentComplete(history),
});
} catch (metadataErr) {
// Non-critical: log but don't fail the agent execution
logger.debug(
"Failed to update metadata after turn (non-critical)",
metadataErr as any,
@@ -552,8 +522,7 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
} catch (e: any) {
if (e.name === "AbortError") {
logger.debug("Response interrupted");
// Remove any partial assistant message
removePartialAssistantMessage(state);
removePartialAssistantMessage(state.session.history);
} else {
logger.error(`Error: ${formatError(e)}`);
@@ -655,7 +624,3 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
});
});
}
// Function moved to serve.helpers.ts - remove implementation
// async function streamChatResponseWithInterruption - moved to helpers {
// Implementation moved to serve.helpers.ts

View File

@@ -1,5 +1,7 @@
import type { ChatHistoryItem } from "core/index.js";
import { env } from "../env.js";
import {
post,
ApiRequestError,
@@ -124,6 +126,9 @@ export async function postAgentMetadata(
agentId: string,
metadata: Record<string, any>,
): Promise<void> {
const endpoint = `agents/${agentId}/metadata`;
const fullUrl = new URL(endpoint, env.apiBase).toString();
if (!agentId) {
logger.debug("No agent ID provided, skipping metadata update");
return;
@@ -134,36 +139,44 @@ export async function postAgentMetadata(
return;
}
try {
logger.debug("Posting metadata to control plane", {
agentId,
metadata,
});
const startTime = Date.now();
logger.info(`[metadata] POST ${fullUrl}`, {
agentId,
metadataKeys: Object.keys(metadata),
});
logger.info("[metadata] Request body", { metadata });
const response = await post(`agents/${agentId}/metadata`, { metadata });
try {
const response = await post(endpoint, { metadata });
const duration = Date.now() - startTime;
if (response.ok) {
logger.info("Successfully posted metadata to control plane");
logger.info(
`[metadata] Success: ${response.status} (${duration}ms) ${fullUrl}`,
);
} else {
logger.warn(
`Unexpected response when posting metadata: ${response.status}`,
`[metadata] Unexpected response: ${response.status} (${duration}ms) ${fullUrl}`,
);
}
} catch (error) {
const duration = Date.now() - startTime;
// Non-critical: Log but don't fail the entire agent execution
if (error instanceof AuthenticationRequiredError) {
logger.debug(
"Authentication required for metadata update (skipping)",
error.message,
logger.info(
`[metadata] Auth required (skipping) (${duration}ms) ${fullUrl}`,
);
} else if (error instanceof ApiRequestError) {
logger.warn(
`Failed to post metadata: ${error.status} ${error.response || error.statusText}`,
`[metadata] Failed: ${error.status} ${error.statusText} (${duration}ms) ${fullUrl}`,
{ response: error.response },
);
} else {
const errorMessage =
error instanceof Error ? error.message : String(error);
logger.warn(`Error posting metadata: ${errorMessage}`);
logger.warn(
`[metadata] Error: ${errorMessage} (${duration}ms) ${fullUrl}`,
);
}
}
}