refactor(health): move health reporting to Railway executor

Health status is now reported from the executor - the single point where
all tools run. This ensures consistent health tracking regardless of
client (playground, direct API, etc).

- Add reportToolHealth() to Railway executor
- Report success/failure after every tool execution
- Remove health reporting from playground (executor handles it)
- Executor calls /api/tools/report-health which has all the logic
This commit is contained in:
Ajax Davis 2025-12-12 05:23:27 +10:00
parent 3d00dee042
commit 1ae6923d1a
2 changed files with 46 additions and 65 deletions

View file

@ -37,41 +37,6 @@ function getConversationEnv(conversationId: string): Record<string, string> {
return conversationEnv.get(conversationId) || {};
}
// Web app API URL for health status updates
const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com';
/**
* Report tool execution result to centralized health service
* Non-blocking - calls web app API which has all the health logic
*/
async function reportToolResult(
packageName: string,
exportName: string,
success: boolean,
error?: string
): Promise<void> {
try {
// Call the web app's centralized health report endpoint
const response = await fetch(`${TPMJS_API_URL}/api/tools/report-health`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
success,
error,
}),
});
if (!response.ok) {
console.warn(`⚠️ Failed to report health status: ${response.status}`);
}
} catch (err) {
// Non-blocking - just log the error
console.error('❌ Failed to report tool result:', err);
}
}
/**
* Dynamically load a tool via Railway service
* Railway service runs with --experimental-network-imports and can import from esm.sh
@ -126,15 +91,6 @@ export async function loadToolDynamically(
if (!response.ok) {
const errorText = await response.text();
console.error(`❌ Railway service error (${response.status}): ${errorText}`);
// Report failure to centralized health service (non-blocking)
reportToolResult(
packageName,
exportName,
false,
`Railway service error (${response.status}): ${errorText}`
);
return null;
}
@ -143,10 +99,6 @@ export async function loadToolDynamically(
if (!data || !data.success) {
const errorMsg = data?.error || 'Unknown error';
console.error(`❌ Failed to load tool: ${errorMsg}`);
// Report failure to centralized health service (non-blocking)
reportToolResult(packageName, exportName, false, errorMsg);
return null;
}
} catch (fetchError) {
@ -154,14 +106,6 @@ export async function loadToolDynamically(
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
console.error(`❌ Railway request timeout after 120s for ${packageName}/${exportName}`);
reportToolResult(
packageName,
exportName,
false,
'Railway service timeout (120s) - tool dependencies may be too large'
);
return null;
}
@ -212,18 +156,11 @@ export async function loadToolDynamically(
if (!result.success) {
console.error(`❌ Tool execution failed: ${result.error}`);
// Report failure to centralized health service (non-blocking)
reportToolResult(packageName, exportName, false, result.error || 'Tool execution failed');
throw new Error(result.error || 'Tool execution failed');
}
// Health status is reported by the Railway executor
console.log(`✅ Tool executed successfully in ${result.executionTimeMs}ms`);
// Report success to centralized health service (non-blocking)
reportToolResult(packageName, exportName, true);
return result.output;
},
});

View file

@ -10,6 +10,44 @@ import { zodToJsonSchema } from 'https://esm.sh/zod-to-json-schema@3.25.0';
// biome-ignore lint/suspicious/noExplicitAny: Tool types are dynamic and vary by package
const moduleCache = new Map<string, any>();
// Web app API URL for health status reporting
const TPMJS_API_URL = Deno.env.get('TPMJS_API_URL') || 'https://tpmjs.com';
/**
* Report tool execution result to centralized health service
* Non-blocking - fires and forgets to avoid slowing down execution
*/
async function reportToolHealth(
packageName: string,
exportName: string,
success: boolean,
error?: string
): Promise<void> {
try {
const response = await fetch(`${TPMJS_API_URL}/api/tools/report-health`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
success,
error,
}),
});
if (response.ok) {
console.log(
`📊 Health reported for ${packageName}/${exportName}: ${success ? 'SUCCESS' : 'FAILURE'}`
);
} else {
console.warn(`⚠️ Failed to report health: ${response.status}`);
}
} catch (err) {
// Non-blocking - just log
console.error('❌ Failed to report tool health:', err);
}
}
/**
* Sanitize JSON Schema to fix common issues
* - Replaces invalid type "None" with "object"
@ -546,15 +584,21 @@ async function executeTool(req: Request): Promise<Response> {
const executionTimeMs = Date.now() - startTime;
console.log(`✅ Execution complete in ${executionTimeMs}ms`);
// Report successful execution to health service (non-blocking)
reportToolHealth(packageName, exportName, true).catch(() => {});
return Response.json({
success: true,
output: result,
executionTimeMs,
});
} catch (error) {
const executionTimeMs = Date.now() - Date.now();
const executionTimeMs = Date.now() - startTime;
console.error('❌ Tool execution failed:', error);
// Report failed execution to health service (non-blocking)
reportToolHealth(packageName, exportName, false, error.message).catch(() => {});
return Response.json(
{
success: false,