Compare commits
3 Commits
main
...
anthropic-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
012a83cdb2 | ||
|
|
e85abaca2b | ||
|
|
07bc85b7fb |
@ -12,6 +12,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- CLI: add live auth probes to `clawdbot models status` for per-profile verification.
|
- CLI: add live auth probes to `clawdbot models status` for per-profile verification.
|
||||||
- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`.
|
- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`.
|
||||||
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.
|
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.
|
||||||
|
- Agents: add diagnostics-configured Anthropic payload logging. (#1501) Thanks @parubets.
|
||||||
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
|
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
|
||||||
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
|
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
|
||||||
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
||||||
|
|||||||
@ -95,6 +95,29 @@ Console logs are **TTY-aware** and formatted for readability:
|
|||||||
|
|
||||||
Console formatting is controlled by `logging.consoleStyle`.
|
Console formatting is controlled by `logging.consoleStyle`.
|
||||||
|
|
||||||
|
## Anthropic payload log (debugging)
|
||||||
|
|
||||||
|
For Anthropic-only runs, you can enable a dedicated JSONL log that captures the
|
||||||
|
exact request payload (as sent) plus per-run usage stats. This log includes full
|
||||||
|
prompt/message data; treat it as sensitive.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnostics": {
|
||||||
|
"anthropicPayloadLog": {
|
||||||
|
"enabled": true,
|
||||||
|
"filePath": "/path/to/anthropic-payload.jsonl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults + overrides:
|
||||||
|
|
||||||
|
- Default path: `$CLAWDBOT_STATE_DIR/logs/anthropic-payload.jsonl`
|
||||||
|
- Env enable: `CLAWDBOT_ANTHROPIC_PAYLOAD_LOG=1`
|
||||||
|
- Env path: `CLAWDBOT_ANTHROPIC_PAYLOAD_LOG_FILE=/path/to/anthropic-payload.jsonl`
|
||||||
|
|
||||||
## Configuring logging
|
## Configuring logging
|
||||||
|
|
||||||
All logging configuration lives under `logging` in `~/.clawdbot/clawdbot.json`.
|
All logging configuration lives under `logging` in `~/.clawdbot/clawdbot.json`.
|
||||||
|
|||||||
182
src/agents/anthropic-payload-log.test.ts
Normal file
182
src/agents/anthropic-payload-log.test.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||||
|
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
import { createAnthropicPayloadLogger } from "./anthropic-payload-log.js";
|
||||||
|
|
||||||
|
describe("createAnthropicPayloadLogger", () => {
|
||||||
|
it("returns null when diagnostics payload logging is disabled", () => {
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {} as ClawdbotConfig,
|
||||||
|
env: {},
|
||||||
|
modelApi: "anthropic-messages",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when model api is not anthropic", () => {
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {
|
||||||
|
diagnostics: {
|
||||||
|
anthropicPayloadLog: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
modelApi: "openai",
|
||||||
|
writer: {
|
||||||
|
filePath: "memory",
|
||||||
|
write: () => undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("honors diagnostics config and expands file paths", () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {
|
||||||
|
diagnostics: {
|
||||||
|
anthropicPayloadLog: {
|
||||||
|
enabled: true,
|
||||||
|
filePath: "~/.clawdbot/logs/anthropic-payload.jsonl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
modelApi: "anthropic-messages",
|
||||||
|
writer: {
|
||||||
|
filePath: "memory",
|
||||||
|
write: (line) => lines.push(line),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger).not.toBeNull();
|
||||||
|
expect(logger?.filePath).toBe(resolveUserPath("~/.clawdbot/logs/anthropic-payload.jsonl"));
|
||||||
|
|
||||||
|
logger?.recordUsage([
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
usage: {
|
||||||
|
input: 12,
|
||||||
|
},
|
||||||
|
} as unknown as {
|
||||||
|
role: string;
|
||||||
|
usage: { input: number };
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const event = JSON.parse(lines[0]?.trim() ?? "{}") as Record<string, unknown>;
|
||||||
|
expect(event.stage).toBe("usage");
|
||||||
|
expect(event.usage).toEqual({ input: 12 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips usage when no new assistant message was added", () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {
|
||||||
|
diagnostics: {
|
||||||
|
anthropicPayloadLog: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
modelApi: "anthropic-messages",
|
||||||
|
writer: {
|
||||||
|
filePath: "memory",
|
||||||
|
write: (line) => lines.push(line),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger?.recordUsage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
usage: {
|
||||||
|
input: 1,
|
||||||
|
},
|
||||||
|
} as unknown as {
|
||||||
|
role: string;
|
||||||
|
usage: { input: number };
|
||||||
|
},
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lines.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("records request payloads and forwards onPayload", async () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
let forwarded: unknown;
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {
|
||||||
|
diagnostics: {
|
||||||
|
anthropicPayloadLog: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
modelApi: "anthropic-messages",
|
||||||
|
writer: {
|
||||||
|
filePath: "memory",
|
||||||
|
write: (line) => lines.push(line),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const streamFn = ((_, __, options) => {
|
||||||
|
options?.onPayload?.({ hello: "world" });
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}) as StreamFn;
|
||||||
|
|
||||||
|
const wrapped = logger?.wrapStreamFn(streamFn);
|
||||||
|
await wrapped?.(
|
||||||
|
{ api: "anthropic-messages" } as unknown as { api: string },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
onPayload: (payload) => {
|
||||||
|
forwarded = payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const event = JSON.parse(lines[0]?.trim() ?? "{}") as Record<string, unknown>;
|
||||||
|
expect(event.stage).toBe("request");
|
||||||
|
expect(event.payload).toEqual({ hello: "world" });
|
||||||
|
expect(event.payloadDigest).toBeTruthy();
|
||||||
|
expect(forwarded).toEqual({ hello: "world" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("records errors when usage is missing", () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const logger = createAnthropicPayloadLogger({
|
||||||
|
cfg: {
|
||||||
|
diagnostics: {
|
||||||
|
anthropicPayloadLog: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
modelApi: "anthropic-messages",
|
||||||
|
writer: {
|
||||||
|
filePath: "memory",
|
||||||
|
write: (line) => lines.push(line),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger?.recordUsage([], new Error("boom"));
|
||||||
|
|
||||||
|
const event = JSON.parse(lines[0]?.trim() ?? "{}") as Record<string, unknown>;
|
||||||
|
expect(event.stage).toBe("usage");
|
||||||
|
expect(event.error).toContain("boom");
|
||||||
|
});
|
||||||
|
});
|
||||||
224
src/agents/anthropic-payload-log.ts
Normal file
224
src/agents/anthropic-payload-log.ts
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import type { AgentMessage, StreamFn } from "@mariozechner/pi-agent-core";
|
||||||
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { resolveStateDir } from "../config/paths.js";
|
||||||
|
import { formatErrorMessage } from "../infra/errors.js";
|
||||||
|
import { parseBooleanValue } from "../utils/boolean.js";
|
||||||
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
|
|
||||||
|
type PayloadLogStage = "request" | "usage";
|
||||||
|
|
||||||
|
type PayloadLogEvent = {
|
||||||
|
ts: string;
|
||||||
|
stage: PayloadLogStage;
|
||||||
|
runId?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
sessionKey?: string;
|
||||||
|
provider?: string;
|
||||||
|
modelId?: string;
|
||||||
|
modelApi?: string | null;
|
||||||
|
workspaceDir?: string;
|
||||||
|
payload?: unknown;
|
||||||
|
usage?: Record<string, unknown>;
|
||||||
|
error?: string;
|
||||||
|
payloadDigest?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PayloadLogConfig = {
|
||||||
|
enabled: boolean;
|
||||||
|
filePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PayloadLogInit = {
|
||||||
|
cfg?: ClawdbotConfig;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PayloadLogWriter = {
|
||||||
|
filePath: string;
|
||||||
|
write: (line: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writers = new Map<string, PayloadLogWriter>();
|
||||||
|
const log = createSubsystemLogger("agent/anthropic-payload");
|
||||||
|
|
||||||
|
function resolvePayloadLogConfig(params: PayloadLogInit): PayloadLogConfig {
|
||||||
|
const env = params.env ?? process.env;
|
||||||
|
const config = params.cfg?.diagnostics?.anthropicPayloadLog;
|
||||||
|
const envEnabled = parseBooleanValue(env.CLAWDBOT_ANTHROPIC_PAYLOAD_LOG);
|
||||||
|
const enabled = envEnabled ?? config?.enabled ?? false;
|
||||||
|
const fileOverride = config?.filePath?.trim() || env.CLAWDBOT_ANTHROPIC_PAYLOAD_LOG_FILE?.trim();
|
||||||
|
const filePath = fileOverride
|
||||||
|
? resolveUserPath(fileOverride)
|
||||||
|
: path.join(resolveStateDir(env), "logs", "anthropic-payload.jsonl");
|
||||||
|
return { enabled, filePath };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWriter(filePath: string): PayloadLogWriter {
|
||||||
|
const existing = writers.get(filePath);
|
||||||
|
if (existing) return existing;
|
||||||
|
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
const ready = fs.mkdir(dir, { recursive: true }).catch(() => undefined);
|
||||||
|
let queue = Promise.resolve();
|
||||||
|
|
||||||
|
const writer: PayloadLogWriter = {
|
||||||
|
filePath,
|
||||||
|
write: (line: string) => {
|
||||||
|
queue = queue
|
||||||
|
.then(() => ready)
|
||||||
|
.then(() => fs.appendFile(filePath, line, "utf8"))
|
||||||
|
.catch(() => undefined);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
writers.set(filePath, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeJsonStringify(value: unknown): string | null {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value, (_key, val) => {
|
||||||
|
if (typeof val === "bigint") return val.toString();
|
||||||
|
if (typeof val === "function") return "[Function]";
|
||||||
|
if (val instanceof Error) {
|
||||||
|
return { name: val.name, message: val.message, stack: val.stack };
|
||||||
|
}
|
||||||
|
if (val instanceof Uint8Array) {
|
||||||
|
return { type: "Uint8Array", data: Buffer.from(val).toString("base64") };
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function digest(value: unknown): string | undefined {
|
||||||
|
const serialized = safeJsonStringify(value);
|
||||||
|
if (!serialized) return undefined;
|
||||||
|
return crypto.createHash("sha256").update(serialized).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAnthropicModel(model: Model<Api> | undefined | null): boolean {
|
||||||
|
return (model as { api?: unknown })?.api === "anthropic-messages";
|
||||||
|
}
|
||||||
|
|
||||||
|
function findLastAssistantUsage(
|
||||||
|
messages: AgentMessage[],
|
||||||
|
minIndex = 0,
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||||
|
if (i < minIndex) break;
|
||||||
|
const msg = messages[i] as { role?: unknown; usage?: unknown };
|
||||||
|
if (msg?.role === "assistant" && msg.usage && typeof msg.usage === "object") {
|
||||||
|
return msg.usage as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AnthropicPayloadLogger = {
|
||||||
|
enabled: true;
|
||||||
|
filePath: string;
|
||||||
|
wrapStreamFn: (streamFn: StreamFn) => StreamFn;
|
||||||
|
recordUsage: (messages: AgentMessage[], error?: unknown, baselineMessageCount?: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createAnthropicPayloadLogger(params: {
|
||||||
|
cfg?: ClawdbotConfig;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
runId?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
sessionKey?: string;
|
||||||
|
provider?: string;
|
||||||
|
modelId?: string;
|
||||||
|
modelApi?: string | null;
|
||||||
|
workspaceDir?: string;
|
||||||
|
writer?: PayloadLogWriter;
|
||||||
|
}): AnthropicPayloadLogger | null {
|
||||||
|
const env = params.env ?? process.env;
|
||||||
|
const cfg = resolvePayloadLogConfig({ env, cfg: params.cfg });
|
||||||
|
if (!cfg.enabled) return null;
|
||||||
|
if (params.modelApi !== "anthropic-messages") return null;
|
||||||
|
|
||||||
|
const writer = params.writer ?? getWriter(cfg.filePath);
|
||||||
|
const base: Omit<PayloadLogEvent, "ts" | "stage"> = {
|
||||||
|
runId: params.runId,
|
||||||
|
sessionId: params.sessionId,
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
provider: params.provider,
|
||||||
|
modelId: params.modelId,
|
||||||
|
modelApi: params.modelApi,
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
const record = (event: PayloadLogEvent) => {
|
||||||
|
const line = safeJsonStringify(event);
|
||||||
|
if (!line) return;
|
||||||
|
writer.write(`${line}\n`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapStreamFn: AnthropicPayloadLogger["wrapStreamFn"] = (streamFn) => {
|
||||||
|
const wrapped: StreamFn = (model, context, options) => {
|
||||||
|
if (!isAnthropicModel(model as Model<Api>)) {
|
||||||
|
return streamFn(model, context, options);
|
||||||
|
}
|
||||||
|
const nextOnPayload = (payload: unknown) => {
|
||||||
|
record({
|
||||||
|
...base,
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
stage: "request",
|
||||||
|
payload,
|
||||||
|
payloadDigest: digest(payload),
|
||||||
|
});
|
||||||
|
options?.onPayload?.(payload);
|
||||||
|
};
|
||||||
|
return streamFn(model, context, {
|
||||||
|
...options,
|
||||||
|
onPayload: nextOnPayload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return wrapped;
|
||||||
|
};
|
||||||
|
|
||||||
|
const recordUsage: AnthropicPayloadLogger["recordUsage"] = (
|
||||||
|
messages,
|
||||||
|
error,
|
||||||
|
baselineMessageCount,
|
||||||
|
) => {
|
||||||
|
const usage = findLastAssistantUsage(messages, baselineMessageCount ?? 0);
|
||||||
|
if (!usage) {
|
||||||
|
if (error) {
|
||||||
|
record({
|
||||||
|
...base,
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
stage: "usage",
|
||||||
|
error: formatErrorMessage(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
record({
|
||||||
|
...base,
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
stage: "usage",
|
||||||
|
usage,
|
||||||
|
error: error ? formatErrorMessage(error) : undefined,
|
||||||
|
});
|
||||||
|
log.info("anthropic usage", {
|
||||||
|
runId: params.runId,
|
||||||
|
sessionId: params.sessionId,
|
||||||
|
usage,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info("anthropic payload logger enabled", { filePath: cfg.filePath });
|
||||||
|
return { enabled: true, filePath: cfg.filePath, wrapStreamFn, recordUsage };
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@ import { isReasoningTagProvider } from "../../../utils/provider-utils.js";
|
|||||||
import { isSubagentSessionKey } from "../../../routing/session-key.js";
|
import { isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||||
import { resolveUserPath } from "../../../utils.js";
|
import { resolveUserPath } from "../../../utils.js";
|
||||||
import { createCacheTrace } from "../../cache-trace.js";
|
import { createCacheTrace } from "../../cache-trace.js";
|
||||||
|
import { createAnthropicPayloadLogger } from "../../anthropic-payload-log.js";
|
||||||
import { resolveClawdbotAgentDir } from "../../agent-paths.js";
|
import { resolveClawdbotAgentDir } from "../../agent-paths.js";
|
||||||
import { resolveSessionAgentIds } from "../../agent-scope.js";
|
import { resolveSessionAgentIds } from "../../agent-scope.js";
|
||||||
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../../bootstrap-files.js";
|
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../../bootstrap-files.js";
|
||||||
@ -458,6 +459,17 @@ export async function runEmbeddedAttempt(
|
|||||||
modelApi: params.model.api,
|
modelApi: params.model.api,
|
||||||
workspaceDir: params.workspaceDir,
|
workspaceDir: params.workspaceDir,
|
||||||
});
|
});
|
||||||
|
const anthropicPayloadLogger = createAnthropicPayloadLogger({
|
||||||
|
cfg: params.config,
|
||||||
|
env: process.env,
|
||||||
|
runId: params.runId,
|
||||||
|
sessionId: activeSession.sessionId,
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
provider: params.provider,
|
||||||
|
modelId: params.modelId,
|
||||||
|
modelApi: params.model.api,
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
});
|
||||||
|
|
||||||
// Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
|
// Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
|
||||||
activeSession.agent.streamFn = streamSimple;
|
activeSession.agent.streamFn = streamSimple;
|
||||||
@ -478,6 +490,11 @@ export async function runEmbeddedAttempt(
|
|||||||
});
|
});
|
||||||
activeSession.agent.streamFn = cacheTrace.wrapStreamFn(activeSession.agent.streamFn);
|
activeSession.agent.streamFn = cacheTrace.wrapStreamFn(activeSession.agent.streamFn);
|
||||||
}
|
}
|
||||||
|
if (anthropicPayloadLogger) {
|
||||||
|
activeSession.agent.streamFn = anthropicPayloadLogger.wrapStreamFn(
|
||||||
|
activeSession.agent.streamFn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prior = await sanitizeSessionHistory({
|
const prior = await sanitizeSessionHistory({
|
||||||
@ -626,6 +643,7 @@ export async function runEmbeddedAttempt(
|
|||||||
|
|
||||||
let messagesSnapshot: AgentMessage[] = [];
|
let messagesSnapshot: AgentMessage[] = [];
|
||||||
let sessionIdUsed = activeSession.sessionId;
|
let sessionIdUsed = activeSession.sessionId;
|
||||||
|
let promptStartMessageCount = activeSession.messages.length;
|
||||||
const onAbort = () => {
|
const onAbort = () => {
|
||||||
const reason = params.abortSignal ? getAbortReason(params.abortSignal) : undefined;
|
const reason = params.abortSignal ? getAbortReason(params.abortSignal) : undefined;
|
||||||
const timeout = reason ? isTimeoutError(reason) : false;
|
const timeout = reason ? isTimeoutError(reason) : false;
|
||||||
@ -697,6 +715,8 @@ export async function runEmbeddedAttempt(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
promptStartMessageCount = activeSession.messages.length;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Detect and load images referenced in the prompt for vision-capable models.
|
// Detect and load images referenced in the prompt for vision-capable models.
|
||||||
// This eliminates the need for an explicit "view" tool call by injecting
|
// This eliminates the need for an explicit "view" tool call by injecting
|
||||||
@ -772,6 +792,7 @@ export async function runEmbeddedAttempt(
|
|||||||
messages: messagesSnapshot,
|
messages: messagesSnapshot,
|
||||||
note: promptError ? "prompt error" : undefined,
|
note: promptError ? "prompt error" : undefined,
|
||||||
});
|
});
|
||||||
|
anthropicPayloadLogger?.recordUsage(messagesSnapshot, promptError, promptStartMessageCount);
|
||||||
|
|
||||||
// Run agent_end hooks to allow plugins to analyze the conversation
|
// Run agent_end hooks to allow plugins to analyze the conversation
|
||||||
// This is fire-and-forget, so we don't await
|
// This is fire-and-forget, so we don't await
|
||||||
|
|||||||
@ -121,6 +121,8 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"diagnostics.cacheTrace.includeMessages": "Cache Trace Include Messages",
|
"diagnostics.cacheTrace.includeMessages": "Cache Trace Include Messages",
|
||||||
"diagnostics.cacheTrace.includePrompt": "Cache Trace Include Prompt",
|
"diagnostics.cacheTrace.includePrompt": "Cache Trace Include Prompt",
|
||||||
"diagnostics.cacheTrace.includeSystem": "Cache Trace Include System",
|
"diagnostics.cacheTrace.includeSystem": "Cache Trace Include System",
|
||||||
|
"diagnostics.anthropicPayloadLog.enabled": "Anthropic Payload Log Enabled",
|
||||||
|
"diagnostics.anthropicPayloadLog.filePath": "Anthropic Payload Log File Path",
|
||||||
"agents.list.*.identity.avatar": "Identity Avatar",
|
"agents.list.*.identity.avatar": "Identity Avatar",
|
||||||
"gateway.remote.url": "Remote Gateway URL",
|
"gateway.remote.url": "Remote Gateway URL",
|
||||||
"gateway.remote.sshTarget": "Remote Gateway SSH Target",
|
"gateway.remote.sshTarget": "Remote Gateway SSH Target",
|
||||||
@ -390,6 +392,10 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"Include full message payloads in trace output (default: true).",
|
"Include full message payloads in trace output (default: true).",
|
||||||
"diagnostics.cacheTrace.includePrompt": "Include prompt text in trace output (default: true).",
|
"diagnostics.cacheTrace.includePrompt": "Include prompt text in trace output (default: true).",
|
||||||
"diagnostics.cacheTrace.includeSystem": "Include system prompt in trace output (default: true).",
|
"diagnostics.cacheTrace.includeSystem": "Include system prompt in trace output (default: true).",
|
||||||
|
"diagnostics.anthropicPayloadLog.enabled":
|
||||||
|
"Log Anthropic request payloads + usage for embedded runs (default: false).",
|
||||||
|
"diagnostics.anthropicPayloadLog.filePath":
|
||||||
|
"JSONL output path for Anthropic payload logs (default: $CLAWDBOT_STATE_DIR/logs/anthropic-payload.jsonl).",
|
||||||
"tools.exec.applyPatch.enabled":
|
"tools.exec.applyPatch.enabled":
|
||||||
"Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.",
|
"Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.",
|
||||||
"tools.exec.applyPatch.allowModels":
|
"tools.exec.applyPatch.allowModels":
|
||||||
|
|||||||
@ -133,10 +133,16 @@ export type DiagnosticsCacheTraceConfig = {
|
|||||||
includeSystem?: boolean;
|
includeSystem?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DiagnosticsAnthropicPayloadLogConfig = {
|
||||||
|
enabled?: boolean;
|
||||||
|
filePath?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type DiagnosticsConfig = {
|
export type DiagnosticsConfig = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
otel?: DiagnosticsOtelConfig;
|
otel?: DiagnosticsOtelConfig;
|
||||||
cacheTrace?: DiagnosticsCacheTraceConfig;
|
cacheTrace?: DiagnosticsCacheTraceConfig;
|
||||||
|
anthropicPayloadLog?: DiagnosticsAnthropicPayloadLogConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebReconnectConfig = {
|
export type WebReconnectConfig = {
|
||||||
|
|||||||
@ -86,6 +86,13 @@ export const ClawdbotSchema = z
|
|||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
anthropicPayloadLog: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
filePath: z.string().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user