server(codex): normalize file paths, truncate large diffs, and drop turn/diff/updated telemetry
This commit is contained in:
@@ -7,3 +7,4 @@ dist
|
|||||||
conversations
|
conversations
|
||||||
.cache
|
.cache
|
||||||
.debug/
|
.debug/
|
||||||
|
.env.test
|
||||||
|
|||||||
@@ -676,12 +676,26 @@ function planStepsToTodoItems(steps: Array<{ step: string; status: string }>): {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCodexFilePath(filePath: unknown, cwd: string | null | undefined): string | null {
|
||||||
|
if (typeof filePath !== "string") return null;
|
||||||
|
const trimmed = filePath.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
if (typeof cwd === "string" && cwd.trim().length > 0) {
|
||||||
|
const normalizedCwd = cwd.endsWith(path.sep) ? cwd : `${cwd}${path.sep}`;
|
||||||
|
if (trimmed.startsWith(normalizedCwd)) {
|
||||||
|
return trimmed.slice(normalizedCwd.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
function threadItemToTimeline(
|
function threadItemToTimeline(
|
||||||
item: any,
|
item: any,
|
||||||
options?: { includeUserMessage?: boolean }
|
options?: { includeUserMessage?: boolean; cwd?: string | null }
|
||||||
): AgentTimelineItem | null {
|
): AgentTimelineItem | null {
|
||||||
if (!item || typeof item !== "object") return null;
|
if (!item || typeof item !== "object") return null;
|
||||||
const includeUserMessage = options?.includeUserMessage ?? true;
|
const includeUserMessage = options?.includeUserMessage ?? true;
|
||||||
|
const cwd = options?.cwd ?? null;
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case "userMessage": {
|
case "userMessage": {
|
||||||
if (!includeUserMessage) {
|
if (!includeUserMessage) {
|
||||||
@@ -722,14 +736,17 @@ function threadItemToTimeline(
|
|||||||
case "fileChange": {
|
case "fileChange": {
|
||||||
const files = Array.isArray(item.changes)
|
const files = Array.isArray(item.changes)
|
||||||
? item.changes.map((change: any) => ({
|
? item.changes.map((change: any) => ({
|
||||||
path: change.path,
|
path: normalizeCodexFilePath(change.path, cwd) ?? change.path,
|
||||||
kind: change.kind,
|
kind: change.kind,
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
const outputFiles = Array.isArray(item.changes)
|
const outputFiles = Array.isArray(item.changes)
|
||||||
? item.changes.map((change: any) => ({
|
? item.changes.map((change: any) => ({
|
||||||
path: change.path,
|
path: normalizeCodexFilePath(change.path, cwd) ?? change.path,
|
||||||
patch: change.diff,
|
patch:
|
||||||
|
typeof change.diff === "string"
|
||||||
|
? truncateUtf8Bytes(change.diff, MAX_FILE_PATCH_BYTES).text
|
||||||
|
: change.diff,
|
||||||
kind: change.kind,
|
kind: change.kind,
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
@@ -823,6 +840,21 @@ function normalizeImageData(mimeType: string, data: string): ImageDataPayload {
|
|||||||
return { mimeType, data };
|
return { mimeType, data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function truncateUtf8Bytes(text: string, maxBytes: number): { text: string; truncated: boolean } {
|
||||||
|
if (maxBytes <= 0) {
|
||||||
|
return { text: "", truncated: text.length > 0 };
|
||||||
|
}
|
||||||
|
const bytes = Buffer.byteLength(text, "utf8");
|
||||||
|
if (bytes <= maxBytes) {
|
||||||
|
return { text, truncated: false };
|
||||||
|
}
|
||||||
|
const buffer = Buffer.from(text, "utf8");
|
||||||
|
const sliced = buffer.subarray(0, maxBytes);
|
||||||
|
return { text: sliced.toString("utf8"), truncated: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_FILE_PATCH_BYTES = 128 * 1024;
|
||||||
|
|
||||||
async function writeImageAttachment(mimeType: string, data: string): Promise<string> {
|
async function writeImageAttachment(mimeType: string, data: string): Promise<string> {
|
||||||
const attachmentsDir = path.join(os.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
|
const attachmentsDir = path.join(os.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
|
||||||
await fs.mkdir(attachmentsDir, { recursive: true });
|
await fs.mkdir(attachmentsDir, { recursive: true });
|
||||||
@@ -1064,7 +1096,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
for (const turn of thread.turns) {
|
for (const turn of thread.turns) {
|
||||||
const items = Array.isArray(turn.items) ? turn.items : [];
|
const items = Array.isArray(turn.items) ? turn.items : [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const timelineItem = threadItemToTimeline(item);
|
const timelineItem = threadItemToTimeline(item, { cwd: this.config.cwd ?? null });
|
||||||
if (timelineItem) {
|
if (timelineItem) {
|
||||||
timeline.push(timelineItem);
|
timeline.push(timelineItem);
|
||||||
}
|
}
|
||||||
@@ -1531,15 +1563,11 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
case "turn/diff/updated": {
|
case "turn/diff/updated": {
|
||||||
const diff = (params as any)?.diff;
|
const diff = (params as any)?.diff;
|
||||||
if (typeof diff === "string" && diff.trim().length > 0) {
|
if (typeof diff === "string" && diff.trim().length > 0) {
|
||||||
this.emitEvent({
|
// NOTE: Codex app-server emits frequent `turn/diff/updated` notifications
|
||||||
type: "timeline",
|
// containing a full accumulated unified diff for the *entire turn*.
|
||||||
provider: CODEX_PROVIDER,
|
// This is not a concrete file-change tool call; it is progress telemetry.
|
||||||
item: createToolCallTimelineItem({
|
// We intentionally do NOT store it in the agent timeline to avoid
|
||||||
name: "apply_patch",
|
// snapshot bloat and relay/WebSocket size limits.
|
||||||
status: "running",
|
|
||||||
output: { diff },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1569,7 +1597,10 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
case "item/completed": {
|
case "item/completed": {
|
||||||
const item = (params as any)?.item;
|
const item = (params as any)?.item;
|
||||||
const timelineItem = threadItemToTimeline(item, { includeUserMessage: false });
|
const timelineItem = threadItemToTimeline(item, {
|
||||||
|
includeUserMessage: false,
|
||||||
|
cwd: this.config.cwd ?? null,
|
||||||
|
});
|
||||||
if (timelineItem) {
|
if (timelineItem) {
|
||||||
if (timelineItem.type === "assistant_message" && item?.id) {
|
if (timelineItem.type === "assistant_message" && item?.id) {
|
||||||
const buffered = this.pendingAgentMessages.get(item.id);
|
const buffered = this.pendingAgentMessages.get(item.id);
|
||||||
@@ -1589,7 +1620,10 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
case "item/started": {
|
case "item/started": {
|
||||||
const item = (params as any)?.item;
|
const item = (params as any)?.item;
|
||||||
const timelineItem = threadItemToTimeline(item, { includeUserMessage: false });
|
const timelineItem = threadItemToTimeline(item, {
|
||||||
|
includeUserMessage: false,
|
||||||
|
cwd: this.config.cwd ?? null,
|
||||||
|
});
|
||||||
if (timelineItem && timelineItem.type === "tool_call") {
|
if (timelineItem && timelineItem.type === "tool_call") {
|
||||||
this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
|
this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
|
||||||
}
|
}
|
||||||
@@ -1749,7 +1783,7 @@ export class CodexAppServerAgentClient implements AgentClient {
|
|||||||
const items: AgentTimelineItem[] = [];
|
const items: AgentTimelineItem[] = [];
|
||||||
for (const turn of turns) {
|
for (const turn of turns) {
|
||||||
for (const item of turn.items ?? []) {
|
for (const item of turn.items ?? []) {
|
||||||
const timelineItem = threadItemToTimeline(item);
|
const timelineItem = threadItemToTimeline(item, { cwd });
|
||||||
if (timelineItem) items.push(timelineItem);
|
if (timelineItem) items.push(timelineItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user