- Cache canonicalToolLifecycleData/buildCanonicalToolLifecycleData results in local variables instead of calling twice with identical arguments (ClaudeAdapter x3, CodexAdapter x1) - Extract identical normalizeCommandValue and pushChangedFile helpers into apps/server/src/provider/toolDataUtils.ts, imported by both adapters - Fix toCanonicalJsonValue to preserve null as a valid CanonicalJsonValue instead of silently converting it to undefined via the nullish coalescing operator
Unified · Split
| @@ -107,10 +107,9 @@ function toCanonicalJsonValue(value: unknown): CanonicalJsonValue | undefined { · 106 unmodified lines | ||
| 107 | 107 | if (value === undefined) { |
| 108 | 108 | return undefined; |
| 109 | 109 | } |
| 110 | − const normalized = JSON.parse( | |
| 110 | + return JSON.parse( | |
| 111 | 111 | JSON.stringify(value, (_key, nestedValue) => (nestedValue === undefined ? null : nestedValue)), |
| 112 | − ) as CanonicalJsonValue | null; | |
| 113 | − return normalized ?? undefined; | |
| 112 | + ) as CanonicalJsonValue; | |
| 114 | 113 | } |
| 115 | 114 | |
| 116 | 115 | function buildContextWindowActivityPayload( |
| @@ -75,6 +75,7 @@ import { · 74 unmodified lines | ||
| 75 | 75 | type ProviderAdapterError, |
| 76 | 76 | } from "../Errors.ts"; |
| 77 | 77 | import { ClaudeAdapter, type ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; |
| 78 | +import { normalizeCommandValue, pushChangedFile } from "../toolDataUtils.ts"; | |
| 78 | 79 | import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; |
| 79 | 80 | |
| 80 | 81 | const PROVIDER = "claudeAgent" as const; |
| @@ -747,32 +748,6 @@ function tryParseJsonRecord(value: string): Record<string, unknown> | undefined · 666 unmodified lines | ||
| 747 | 748 | } |
| 748 | 749 | } |
| 749 | 750 | |
| 750 | −function normalizeCommandValue(value: unknown): string | undefined { | |
| 751 | − if (typeof value === "string") { | |
| 752 | − const trimmed = value.trim(); | |
| 753 | − return trimmed.length > 0 ? trimmed : undefined; | |
| 754 | − } | |
| 755 | − if (!Array.isArray(value)) { | |
| 756 | − return undefined; | |
| 757 | − } | |
| 758 | − const parts = value | |
| 759 | − .map((entry) => (typeof entry === "string" ? entry.trim() : "")) | |
| 760 | − .filter((entry) => entry.length > 0); | |
| 761 | − return parts.length > 0 ? parts.join(" ") : undefined; | |
| 762 | −} | |
| 763 | − | |
| 764 | −function pushChangedFile(target: string[], seen: Set<string>, value: unknown) { | |
| 765 | − if (typeof value !== "string") { | |
| 766 | − return; | |
| 767 | − } | |
| 768 | − const normalized = value.trim(); | |
| 769 | − if (normalized.length === 0 || seen.has(normalized)) { | |
| 770 | − return; | |
| 771 | − } | |
| 772 | − seen.add(normalized); | |
| 773 | − target.push(normalized); | |
| 774 | −} | |
| 775 | − | |
| 776 | 751 | function collectChangedFiles(value: unknown, target: string[], seen: Set<string>, depth: number) { |
| 777 | 752 | if (depth > 4 || target.length >= 12) { |
| 778 | 753 | return; |
| @@ -1540,19 +1515,14 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 761 unmodified lines | ||
| 1540 | 1515 | status: status === "completed" ? "completed" : "failed", |
| 1541 | 1516 | title: tool.title, |
| 1542 | 1517 | ...(tool.detail ? { detail: tool.detail } : {}), |
| 1543 | − ...(canonicalToolLifecycleData({ | |
| 1544 | − itemType: tool.itemType, | |
| 1545 | − toolName: tool.toolName, | |
| 1546 | − input: tool.input, | |
| 1547 | − }) | |
| 1548 | − ? { | |
| 1549 | − data: canonicalToolLifecycleData({ | |
| 1550 | − itemType: tool.itemType, | |
| 1551 | − toolName: tool.toolName, | |
| 1552 | − input: tool.input, | |
| 1553 | − }), | |
| 1554 | − } | |
| 1555 | − : {}), | |
| 1518 | + ...(() => { | |
| 1519 | + const toolData = canonicalToolLifecycleData({ | |
| 1520 | + itemType: tool.itemType, | |
| 1521 | + toolName: tool.toolName, | |
| 1522 | + input: tool.input, | |
| 1523 | + }); | |
| 1524 | + return toolData ? { data: toolData } : {}; | |
| 1525 | + })(), | |
| 1556 | 1526 | }, |
| 1557 | 1527 | providerRefs: nativeProviderRefs(context, { providerItemId: tool.itemId }), |
| 1558 | 1528 | raw: { |
| @@ -1744,19 +1714,14 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 185 unmodified lines | ||
| 1744 | 1714 | status: "inProgress", |
| 1745 | 1715 | title: nextTool.title, |
| 1746 | 1716 | ...(nextTool.detail ? { detail: nextTool.detail } : {}), |
| 1747 | − ...(canonicalToolLifecycleData({ | |
| 1748 | − itemType: nextTool.itemType, | |
| 1749 | − toolName: nextTool.toolName, | |
| 1750 | − input: nextTool.input, | |
| 1751 | − }) | |
| 1752 | − ? { | |
| 1753 | − data: canonicalToolLifecycleData({ | |
| 1754 | − itemType: nextTool.itemType, | |
| 1755 | − toolName: nextTool.toolName, | |
| 1756 | − input: nextTool.input, | |
| 1757 | − }), | |
| 1758 | − } | |
| 1759 | − : {}), | |
| 1717 | + ...(() => { | |
| 1718 | + const toolData = canonicalToolLifecycleData({ | |
| 1719 | + itemType: nextTool.itemType, | |
| 1720 | + toolName: nextTool.toolName, | |
| 1721 | + input: nextTool.input, | |
| 1722 | + }); | |
| 1723 | + return toolData ? { data: toolData } : {}; | |
| 1724 | + })(), | |
| 1760 | 1725 | }, |
| 1761 | 1726 | providerRefs: nativeProviderRefs(context, { providerItemId: nextTool.itemId }), |
| 1762 | 1727 | raw: { |
| @@ -1822,19 +1787,14 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 59 unmodified lines | ||
| 1822 | 1787 | status: "inProgress", |
| 1823 | 1788 | title: tool.title, |
| 1824 | 1789 | ...(tool.detail ? { detail: tool.detail } : {}), |
| 1825 | − ...(canonicalToolLifecycleData({ | |
| 1826 | − itemType: tool.itemType, | |
| 1827 | − toolName: tool.toolName, | |
| 1828 | − input: toolInput, | |
| 1829 | − }) | |
| 1830 | − ? { | |
| 1831 | − data: canonicalToolLifecycleData({ | |
| 1832 | − itemType: tool.itemType, | |
| 1833 | − toolName: tool.toolName, | |
| 1834 | − input: toolInput, | |
| 1835 | − }), | |
| 1836 | − } | |
| 1837 | − : {}), | |
| 1790 | + ...(() => { | |
| 1791 | + const toolData = canonicalToolLifecycleData({ | |
| 1792 | + itemType: tool.itemType, | |
| 1793 | + toolName: tool.toolName, | |
| 1794 | + input: toolInput, | |
| 1795 | + }); | |
| 1796 | + return toolData ? { data: toolData } : {}; | |
| 1797 | + })(), | |
| 1838 | 1798 | }, |
| 1839 | 1799 | providerRefs: nativeProviderRefs(context, { providerItemId: tool.itemId }), |
| 1840 | 1800 | raw: { |
| @@ -33,6 +33,7 @@ import { · 32 unmodified lines | ||
| 33 | 33 | type ProviderAdapterError, |
| 34 | 34 | } from "../Errors.ts"; |
| 35 | 35 | import { CodexAdapter, type CodexAdapterShape } from "../Services/CodexAdapter.ts"; |
| 36 | +import { normalizeCommandValue, pushChangedFile } from "../toolDataUtils.ts"; | |
| 36 | 37 | import { |
| 37 | 38 | CodexAppServerManager, |
| 38 | 39 | type CodexAppServerStartSessionInput, |
| @@ -111,32 +112,6 @@ function asNumber(value: unknown): number | undefined { · 72 unmodified lines | ||
| 111 | 112 | return typeof value === "number" && Number.isFinite(value) ? value : undefined; |
| 112 | 113 | } |
| 113 | 114 | |
| 114 | −function normalizeCommandValue(value: unknown): string | undefined { | |
| 115 | − if (typeof value === "string") { | |
| 116 | − const trimmed = value.trim(); | |
| 117 | − return trimmed.length > 0 ? trimmed : undefined; | |
| 118 | − } | |
| 119 | − if (!Array.isArray(value)) { | |
| 120 | − return undefined; | |
| 121 | − } | |
| 122 | − const parts = value | |
| 123 | − .map((entry) => (typeof entry === "string" ? entry.trim() : "")) | |
| 124 | − .filter((entry) => entry.length > 0); | |
| 125 | − return parts.length > 0 ? parts.join(" ") : undefined; | |
| 126 | −} | |
| 127 | − | |
| 128 | −function pushChangedFile(target: string[], seen: Set<string>, value: unknown) { | |
| 129 | − if (typeof value !== "string") { | |
| 130 | − return; | |
| 131 | − } | |
| 132 | − const normalized = value.trim(); | |
| 133 | − if (normalized.length === 0 || seen.has(normalized)) { | |
| 134 | − return; | |
| 135 | − } | |
| 136 | − seen.add(normalized); | |
| 137 | − target.push(normalized); | |
| 138 | −} | |
| 139 | − | |
| 140 | 115 | function collectChangedFiles(value: unknown, target: string[], seen: Set<string>, depth: number) { |
| 141 | 116 | if (depth > 4 || target.length >= 12) { |
| 142 | 117 | return; |
| @@ -670,9 +645,10 @@ function mapItemLifecycle( · 527 unmodified lines | ||
| 670 | 645 | ...(status ? { status } : {}), |
| 671 | 646 | ...(itemTitle(itemType) ? { title: itemTitle(itemType) } : {}), |
| 672 | 647 | ...(detail ? { detail } : {}), |
| 673 | − ...(buildCanonicalToolLifecycleData(itemType, source, payload ?? {}) | |
| 674 | − ? { data: buildCanonicalToolLifecycleData(itemType, source, payload ?? {}) } | |
| 675 | − : {}), | |
| 648 | + ...(() => { | |
| 649 | + const toolData = buildCanonicalToolLifecycleData(itemType, source, payload ?? {}); | |
| 650 | + return toolData ? { data: toolData } : {}; | |
| 651 | + })(), | |
| 676 | 652 | }, |
| 677 | 653 | }; |
| 678 | 654 | } |
| @@ -0,0 +1,25 @@ | ||
| 1 | +export function normalizeCommandValue(value: unknown): string | undefined { | |
| 2 | + if (typeof value === "string") { | |
| 3 | + const trimmed = value.trim(); | |
| 4 | + return trimmed.length > 0 ? trimmed : undefined; | |
| 5 | + } | |
| 6 | + if (!Array.isArray(value)) { | |
| 7 | + return undefined; | |
| 8 | + } | |
| 9 | + const parts = value | |
| 10 | + .map((entry) => (typeof entry === "string" ? entry.trim() : "")) | |
| 11 | + .filter((entry) => entry.length > 0); | |
| 12 | + return parts.length > 0 ? parts.join(" ") : undefined; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export function pushChangedFile(target: string[], seen: Set<string>, value: unknown) { | |
| 16 | + if (typeof value !== "string") { | |
| 17 | + return; | |
| 18 | + } | |
| 19 | + const normalized = value.trim(); | |
| 20 | + if (normalized.length === 0 || seen.has(normalized)) { | |
| 21 | + return; | |
| 22 | + } | |
| 23 | + seen.add(normalized); | |
| 24 | + target.push(normalized); | |
| 25 | +} | |