ML-160: enable llm access to production logs
This commit is contained in:
@@ -14,6 +14,13 @@
|
|||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
import { BorderedLoader, DynamicBorder } from "@mariozechner/pi-coding-agent";
|
import { BorderedLoader, DynamicBorder } from "@mariozechner/pi-coding-agent";
|
||||||
|
import {
|
||||||
|
truncateTail,
|
||||||
|
formatSize,
|
||||||
|
DEFAULT_MAX_BYTES,
|
||||||
|
DEFAULT_MAX_LINES,
|
||||||
|
} from "@mariozechner/pi-coding-agent";
|
||||||
|
import { Type } from "typebox";
|
||||||
import {
|
import {
|
||||||
matchesKey,
|
matchesKey,
|
||||||
Key,
|
Key,
|
||||||
@@ -309,6 +316,143 @@ async function fetchLogs(
|
|||||||
// ── Extension ───────────────────────────────────────────────────────────────
|
// ── Extension ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export default function prodLogsExtension(pi: ExtensionAPI) {
|
export default function prodLogsExtension(pi: ExtensionAPI) {
|
||||||
|
// ── fetch_production_logs tool ─────────────────────────────────────────
|
||||||
|
|
||||||
|
pi.registerTool({
|
||||||
|
name: "fetch_production_logs",
|
||||||
|
label: "Fetch Production Logs",
|
||||||
|
description:
|
||||||
|
"Fetch recent production logs from the deployed application via the Coolify API. " +
|
||||||
|
"Use this tool when investigating production errors, checking server behavior, " +
|
||||||
|
"debugging deployed issues, or when the user asks about production status, " +
|
||||||
|
"logs, errors, or application behavior in the deployed environment. " +
|
||||||
|
"Output is truncated at 50KB / 2000 lines — use a smaller 'tail' value or " +
|
||||||
|
"a narrower 'grep' pattern to reduce output if truncation occurs.",
|
||||||
|
promptSnippet:
|
||||||
|
"Fetch recent production logs from deployed app (param: tail, grep)",
|
||||||
|
promptGuidelines: [
|
||||||
|
"Use fetch_production_logs when investigating production errors, checking deployed application behavior, or when the user asks about production status or recent activity.",
|
||||||
|
"Use the 'grep' parameter to filter logs by a case-insensitive substring (e.g., grep: 'error', grep: 'timeout'). This reduces output size and helps find relevant entries.",
|
||||||
|
"Start with a small 'tail' value (e.g., tail: 50) and increase only if needed. Large tail values may hit the 50KB/2000-line truncation limit.",
|
||||||
|
],
|
||||||
|
parameters: Type.Object({
|
||||||
|
tail: Type.Optional(
|
||||||
|
Type.Number({
|
||||||
|
description:
|
||||||
|
"Number of most recent log lines to return. Default: 200. Use a smaller value to reduce output size.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
grep: Type.Optional(
|
||||||
|
Type.String({
|
||||||
|
description:
|
||||||
|
"Case-insensitive substring filter for log lines. Only lines containing this string are returned. Use to narrow results to relevant entries.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
async execute(
|
||||||
|
_toolCallId,
|
||||||
|
params,
|
||||||
|
signal,
|
||||||
|
_onUpdate,
|
||||||
|
_ctx,
|
||||||
|
) {
|
||||||
|
// Early abort check
|
||||||
|
if (signal?.aborted) {
|
||||||
|
return { content: [{ type: "text", text: "Cancelled" }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate credentials
|
||||||
|
const host = resolveVar("coolify_host");
|
||||||
|
const appUuid = resolveVar("coolify_app_uuid");
|
||||||
|
const token = resolveVar("coolify_token");
|
||||||
|
|
||||||
|
const missing: string[] = [];
|
||||||
|
if (!host) missing.push("HURL_VARIABLE_coolify_host");
|
||||||
|
if (!appUuid) missing.push("HURL_VARIABLE_coolify_app_uuid");
|
||||||
|
if (!token) missing.push("HURL_VARIABLE_coolify_token");
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text:
|
||||||
|
`Cannot fetch production logs: the following environment variables are not set:\n` +
|
||||||
|
missing.map((v) => ` - ${v}`).join("\n") +
|
||||||
|
`\n\nEnsure these are configured in your pi environment.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch logs
|
||||||
|
let lines: string[];
|
||||||
|
try {
|
||||||
|
lines = await fetchLogs(host!, appUuid!, token!, signal);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to fetch production logs: ${message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty logs
|
||||||
|
if (lines.length === 0) {
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: "No log entries found." }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent entries first (consistent with the /prod-logs viewer)
|
||||||
|
lines.reverse();
|
||||||
|
|
||||||
|
// Apply grep filter if provided
|
||||||
|
if (params.grep) {
|
||||||
|
const pattern = params.grep.toLowerCase();
|
||||||
|
lines = lines.filter((line) => line.toLowerCase().includes(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record pre-tail line count for details
|
||||||
|
const lineCount = lines.length;
|
||||||
|
|
||||||
|
// Apply tail limit
|
||||||
|
const tail = params.tail ?? 200;
|
||||||
|
lines = lines.slice(0, tail);
|
||||||
|
|
||||||
|
// Join into a single string
|
||||||
|
let output = lines.join("\n");
|
||||||
|
|
||||||
|
// Apply output truncation
|
||||||
|
const truncation = truncateTail(output, {
|
||||||
|
maxLines: DEFAULT_MAX_LINES,
|
||||||
|
maxBytes: DEFAULT_MAX_BYTES,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (truncation.truncated) {
|
||||||
|
output =
|
||||||
|
truncation.content +
|
||||||
|
`\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). ` +
|
||||||
|
`Use a smaller 'tail' value or narrower 'grep' pattern to reduce output.]`;
|
||||||
|
} else {
|
||||||
|
output = truncation.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: output }],
|
||||||
|
details: { lineCount },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── /prod-logs interactive command ─────────────────────────────────────
|
||||||
|
|
||||||
pi.registerCommand("prod-logs", {
|
pi.registerCommand("prod-logs", {
|
||||||
description: "View production application logs via Coolify API",
|
description: "View production application logs via Coolify API",
|
||||||
handler: async (_args, ctx) => {
|
handler: async (_args, ctx) => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
id: ML-160
|
id: ML-160
|
||||||
title: Programmatic access to production logs
|
title: Programmatic access to production logs
|
||||||
status: To Do
|
status: Done
|
||||||
assignee: []
|
assignee: []
|
||||||
created_date: '2026-05-04 06:42'
|
created_date: '2026-05-04 06:42'
|
||||||
updated_date: '2026-05-04 06:52'
|
updated_date: '2026-05-04 07:33'
|
||||||
labels: []
|
labels: []
|
||||||
dependencies: []
|
dependencies: []
|
||||||
references:
|
references:
|
||||||
@@ -20,13 +20,13 @@ Evaluate and implement the best approach for the LLM to access production logs.
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
<!-- AC:BEGIN -->
|
<!-- AC:BEGIN -->
|
||||||
- [ ] #1 The LLM can fetch production logs without user intervention by calling the fetch_production_logs tool
|
- [x] #1 The LLM can fetch production logs without user intervention by calling the fetch_production_logs tool
|
||||||
- [ ] #2 The tool supports a `tail` parameter to limit the number of log lines returned (default: 200)
|
- [x] #2 The tool supports a `tail` parameter to limit the number of log lines returned (default: 200)
|
||||||
- [ ] #3 The tool supports a `grep` parameter for case-insensitive filtering of log lines
|
- [x] #3 The tool supports a `grep` parameter for case-insensitive filtering of log lines
|
||||||
- [ ] #4 The tool returns log lines as text the LLM can read and analyze directly in context
|
- [x] #4 The tool returns log lines as text the LLM can read and analyze directly in context
|
||||||
- [ ] #5 When Coolify credentials are missing, the tool returns a clear error message listing which environment variables are not set
|
- [x] #5 When Coolify credentials are missing, the tool returns a clear error message listing which environment variables are not set
|
||||||
- [ ] #6 The existing /prod-logs interactive command continues to work unchanged
|
- [x] #6 The existing /prod-logs interactive command continues to work unchanged
|
||||||
- [ ] #7 The tool description and guidelines teach the LLM when and how to use it effectively
|
- [x] #7 The tool description and guidelines teach the LLM when and how to use it effectively
|
||||||
<!-- AC:END -->
|
<!-- AC:END -->
|
||||||
|
|
||||||
## Implementation Plan
|
## Implementation Plan
|
||||||
@@ -143,3 +143,39 @@ The implementation is self-documenting: the tool's `description`, `promptSnippet
|
|||||||
- `truncateTail`, `formatSize`, `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES` — all from `@mariozechner/pi-coding-agent`, a pi built-in
|
- `truncateTail`, `formatSize`, `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES` — all from `@mariozechner/pi-coding-agent`, a pi built-in
|
||||||
- No new npm dependencies needed
|
- No new npm dependencies needed
|
||||||
<!-- SECTION:PLAN:END -->
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Added `fetch_production_logs` tool to the existing `.pi/extensions/prod-logs/index.ts` extension. The tool reuses the existing `fetchLogs()` and `resolveVar()` helper functions. Implementation follows the plan exactly:
|
||||||
|
|
||||||
|
- Added imports for `Type` (typebox) and `truncateTail`, `formatSize`, `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES` (@mariozechner/pi-coding-agent)
|
||||||
|
- Registered tool before the existing `pi.registerCommand("prod-logs", ...)` call
|
||||||
|
- Tool supports `tail` (default 200) and `grep` parameters
|
||||||
|
- Handler: credential check → fetch → error handling → empty check → reverse → grep → tail → join → truncate → return
|
||||||
|
- Truncation via `truncateTail` with 50KB/2000-line limit, with clear truncation note in output
|
||||||
|
- Existing `/prod-logs` command code is completely untouched
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
Added a `fetch_production_logs` tool to `.pi/extensions/prod-logs/index.ts` using `pi.registerTool()`. The tool gives the LLM programmatic access to production logs without user intervention.
|
||||||
|
|
||||||
|
**What changed:**
|
||||||
|
- Added imports for `Type` (typebox) and `truncateTail`, `formatSize`, `DEFAULT_MAX_BYTES`, `DEFAULT_MAX_LINES` from `@mariozechner/pi-coding-agent`
|
||||||
|
- Registered `fetch_production_logs` tool with `tail` (default 200) and `grep` parameters
|
||||||
|
- Tool reuses the existing `fetchLogs()` and `resolveVar()` module-scope functions from the `/prod-logs` extension
|
||||||
|
- Handler: credential validation → fetch → error handling → empty check → reverse (newest first) → grep filter → tail slice → join → `truncateTail` truncation with clear truncation note
|
||||||
|
- On missing credentials, returns a clear error listing which env vars are not set
|
||||||
|
|
||||||
|
**What didn't change:**
|
||||||
|
- The existing `/prod-logs` interactive command code is completely untouched
|
||||||
|
- No new dependencies, env vars, infrastructure changes, or Elixir module changes
|
||||||
|
|
||||||
|
**Verification (requires `/reload` in pi):**
|
||||||
|
1. Ask the LLM about available tools for production logs — should describe `fetch_production_logs`
|
||||||
|
2. Ask the LLM to fetch logs with `tail: 50` or `grep: "error"` — should work
|
||||||
|
3. Unset a credential and try — should get clear error message
|
||||||
|
4. Run `/prod-logs` — should still work unchanged
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
|
|||||||
Reference in New Issue
Block a user