refactor(claude-agent): replace unsafe type assertions with type guards (#707)
Adds isObjectRecord, isUnknownArray, isChildProcessWithStreams, and isImageMimeType type predicates to eliminate all no-unsafe-type-assertion violations surfaced by typeAware lint mode. Fixes floating promise, adds throw after exhaustive switch, and widens readCompactionMetadata to accept unknown so callers need no casts.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
import { type ChildProcess, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { promises } from "node:fs";
|
import { promises } from "node:fs";
|
||||||
@@ -142,11 +142,31 @@ export function normalizeClaudeAskUserQuestionUpdatedInput(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
function toObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
function toObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
return isObjectRecord(value) ? value : undefined;
|
||||||
return undefined;
|
}
|
||||||
}
|
|
||||||
return value as Record<string, unknown>;
|
function isUnknownArray(value: unknown): value is readonly unknown[] {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isChildProcessWithStreams(child: ChildProcess): child is ChildProcessWithoutNullStreams {
|
||||||
|
return child.stdin !== null && child.stdout !== null && child.stderr !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageMimeType(
|
||||||
|
value: string,
|
||||||
|
): value is "image/jpeg" | "image/png" | "image/gif" | "image/webp" {
|
||||||
|
return (
|
||||||
|
value === "image/jpeg" ||
|
||||||
|
value === "image/png" ||
|
||||||
|
value === "image/gif" ||
|
||||||
|
value === "image/webp"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type TurnState = "idle" | "foreground" | "autonomous";
|
type TurnState = "idle" | "foreground" | "autonomous";
|
||||||
@@ -342,7 +362,10 @@ function applyRuntimeSettingsToClaudeOptions(
|
|||||||
options.stderr?.(chunk.toString());
|
options.stderr?.(chunk.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return child as ChildProcessWithoutNullStreams;
|
if (!isChildProcessWithStreams(child)) {
|
||||||
|
throw new Error("Claude process was spawned without stdio streams");
|
||||||
|
}
|
||||||
|
return child;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -559,21 +582,22 @@ function collectClaudeTextContentParts(content: unknown): string[] {
|
|||||||
return normalized ? [normalized] : [];
|
return normalized ? [normalized] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(content)) {
|
if (!isUnknownArray(content)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
for (const block of content) {
|
for (const block of content) {
|
||||||
if (!block || typeof block !== "object") {
|
const blockRecord = toObjectRecord(block);
|
||||||
|
if (!blockRecord) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const text = normalizeClaudeTranscriptText((block as { text?: unknown }).text);
|
const text = normalizeClaudeTranscriptText(blockRecord.text);
|
||||||
if (text) {
|
if (text) {
|
||||||
parts.push(text);
|
parts.push(text);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const input = normalizeClaudeTranscriptText((block as { input?: unknown }).input);
|
const input = normalizeClaudeTranscriptText(blockRecord.input);
|
||||||
if (input) {
|
if (input) {
|
||||||
parts.push(input);
|
parts.push(input);
|
||||||
}
|
}
|
||||||
@@ -787,6 +811,7 @@ function toClaudeSdkMcpConfig(config: McpServerConfig): ClaudeSdkMcpServerConfig
|
|||||||
headers: config.headers,
|
headers: config.headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
throw new Error("Unhandled MCP server config type");
|
||||||
}
|
}
|
||||||
|
|
||||||
function isClaudeContentChunk(value: unknown): value is ClaudeContentChunk {
|
function isClaudeContentChunk(value: unknown): value is ClaudeContentChunk {
|
||||||
@@ -1138,26 +1163,24 @@ class TimelineAssembler {
|
|||||||
private readMessageIdFromAssistantMessage(
|
private readMessageIdFromAssistantMessage(
|
||||||
message: SDKMessage & { type: "assistant" },
|
message: SDKMessage & { type: "assistant" },
|
||||||
): string | null {
|
): string | null {
|
||||||
const candidate = message as unknown as {
|
const candidate = toObjectRecord(message);
|
||||||
message_id?: unknown;
|
const messageContainer = toObjectRecord(candidate?.message);
|
||||||
message?: { id?: unknown } | null;
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
readTrimmedString(candidate.message_id) ?? readTrimmedString(candidate.message?.id) ?? null
|
readTrimmedString(candidate?.message_id) ?? readTrimmedString(messageContainer?.id) ?? null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readMessageIdFromStreamEvent(event: Record<string, unknown>): string | null {
|
private readMessageIdFromStreamEvent(event: Record<string, unknown>): string | null {
|
||||||
const message = event.message as { id?: unknown } | undefined;
|
const messageContainer = toObjectRecord(event.message);
|
||||||
return readTrimmedString(event.message_id) ?? readTrimmedString(message?.id) ?? null;
|
return readTrimmedString(event.message_id) ?? readTrimmedString(messageContainer?.id) ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSyntheticUserEntry(entry: unknown): boolean {
|
function isSyntheticUserEntry(entry: unknown): boolean {
|
||||||
if (!entry || typeof entry !== "object") {
|
const candidate = toObjectRecord(entry);
|
||||||
|
if (!candidate) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const candidate = entry as { isSynthetic?: unknown; isMeta?: unknown };
|
|
||||||
return candidate.isSynthetic === true || candidate.isMeta === true;
|
return candidate.isSynthetic === true || candidate.isMeta === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,11 +1195,11 @@ function firstTrimmedString(sources: readonly unknown[]): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readEventIdentifiers(message: SDKMessage): EventIdentifiers {
|
export function readEventIdentifiers(message: SDKMessage): EventIdentifiers {
|
||||||
const root = message as unknown as Record<string, unknown>;
|
const root = toObjectRecord(message) ?? {};
|
||||||
const messageType = readTrimmedString(root.type);
|
const messageType = readTrimmedString(root.type);
|
||||||
const streamEvent = root.event as Record<string, unknown> | undefined;
|
const streamEvent = toObjectRecord(root.event);
|
||||||
const streamEventMessage = streamEvent?.message as Record<string, unknown> | undefined;
|
const streamEventMessage = toObjectRecord(streamEvent?.message);
|
||||||
const messageContainer = root.message as Record<string, unknown> | undefined;
|
const messageContainer = toObjectRecord(root.message);
|
||||||
|
|
||||||
const messageIdFromUuid = messageType === "user" ? root.uuid : undefined;
|
const messageIdFromUuid = messageType === "user" ? root.uuid : undefined;
|
||||||
|
|
||||||
@@ -1393,15 +1416,11 @@ async function resolveClaudeAuth(
|
|||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as {
|
const err = toObjectRecord(error);
|
||||||
stdout?: string;
|
const stdout = typeof err?.stdout === "string" ? err.stdout : "";
|
||||||
stderr?: string;
|
const stderr = typeof err?.stderr === "string" ? err.stderr : "";
|
||||||
message?: string;
|
const fallbackMessage = typeof err?.message === "string" ? err.message : "";
|
||||||
};
|
return { stdout, stderr: stderr || fallbackMessage };
|
||||||
return {
|
|
||||||
stdout: err.stdout ?? "",
|
|
||||||
stderr: err.stderr ?? err.message ?? "",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1428,16 +1447,18 @@ async function resolveClaudeAuth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractContextWindowSize(modelUsage: unknown): number | undefined {
|
function extractContextWindowSize(modelUsage: unknown): number | undefined {
|
||||||
if (!modelUsage || typeof modelUsage !== "object") {
|
const usageRecord = toObjectRecord(modelUsage);
|
||||||
|
if (!usageRecord) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxContextWindow: number | undefined;
|
let maxContextWindow: number | undefined;
|
||||||
for (const value of Object.values(modelUsage as Record<string, unknown>)) {
|
for (const value of Object.values(usageRecord)) {
|
||||||
if (!value || typeof value !== "object") {
|
const valueRecord = toObjectRecord(value);
|
||||||
|
if (!valueRecord) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const contextWindow = (value as { contextWindow?: unknown }).contextWindow;
|
const contextWindow = valueRecord.contextWindow;
|
||||||
if (
|
if (
|
||||||
typeof contextWindow !== "number" ||
|
typeof contextWindow !== "number" ||
|
||||||
!Number.isFinite(contextWindow) ||
|
!Number.isFinite(contextWindow) ||
|
||||||
@@ -1473,15 +1494,11 @@ function readUsageFromTaskNotification(message: { usage?: unknown }): number | u
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readStreamRequestInputTokens(event: Record<string, unknown>): number | undefined {
|
function readStreamRequestInputTokens(event: Record<string, unknown>): number | undefined {
|
||||||
const messageUsage = (event.message as { usage?: unknown } | undefined)?.usage;
|
const messageUsage = toObjectRecord(toObjectRecord(event.message)?.usage);
|
||||||
if (!messageUsage || typeof messageUsage !== "object") {
|
if (!messageUsage) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const usage = messageUsage as {
|
const usage = messageUsage;
|
||||||
input_tokens?: unknown;
|
|
||||||
cache_creation_input_tokens?: unknown;
|
|
||||||
cache_read_input_tokens?: unknown;
|
|
||||||
};
|
|
||||||
const inputTokens =
|
const inputTokens =
|
||||||
typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
|
typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
|
||||||
? usage.input_tokens
|
? usage.input_tokens
|
||||||
@@ -1503,7 +1520,7 @@ function readStreamRequestInputTokens(event: Record<string, unknown>): number |
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readStreamRequestOutputTokens(event: Record<string, unknown>): number | undefined {
|
function readStreamRequestOutputTokens(event: Record<string, unknown>): number | undefined {
|
||||||
const outputTokens = (event.usage as { output_tokens?: unknown } | undefined)?.output_tokens;
|
const outputTokens = toObjectRecord(event.usage)?.output_tokens;
|
||||||
if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens) || outputTokens < 0) {
|
if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens) || outputTokens < 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -2226,12 +2243,12 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
? this.config.thinkingOptionId
|
? this.config.thinkingOptionId
|
||||||
: undefined;
|
: undefined;
|
||||||
if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
|
if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
|
||||||
// SDK 0.2.71 types `effort` as 'low' | 'medium' | 'high' | 'max'; Opus 4.7
|
if (thinkingOptionId === "xhigh") {
|
||||||
// adds 'xhigh' which the binary accepts but the typings don't yet expose.
|
// "xhigh" is accepted by Claude Opus 4.7 but not yet in the SDK type definitions
|
||||||
return {
|
// @ts-expect-error -- SDK 0.2.71 effort type doesn't include "xhigh" yet
|
||||||
thinking: { type: "adaptive" },
|
return { thinking: { type: "adaptive" }, effort: thinkingOptionId };
|
||||||
effort: thinkingOptionId as ClaudeOptions["effort"],
|
}
|
||||||
};
|
return { thinking: { type: "adaptive" }, effort: thinkingOptionId };
|
||||||
}
|
}
|
||||||
return { thinking: undefined, effort: undefined };
|
return { thinking: undefined, effort: undefined };
|
||||||
}
|
}
|
||||||
@@ -2357,14 +2374,16 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
if (chunk.type === "text") {
|
if (chunk.type === "text") {
|
||||||
content.push({ type: "text", text: chunk.text });
|
content.push({ type: "text", text: chunk.text });
|
||||||
} else if (chunk.type === "image") {
|
} else if (chunk.type === "image") {
|
||||||
content.push({
|
if (isImageMimeType(chunk.mimeType)) {
|
||||||
type: "image",
|
content.push({
|
||||||
source: {
|
type: "image",
|
||||||
type: "base64",
|
source: {
|
||||||
media_type: chunk.mimeType as "image/jpeg" | "image/png" | "image/gif" | "image/webp",
|
type: "base64",
|
||||||
data: chunk.data,
|
media_type: chunk.mimeType,
|
||||||
},
|
data: chunk.data,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content.push({ type: "text", text: renderPromptAttachmentAsText(chunk) });
|
content.push({ type: "text", text: renderPromptAttachmentAsText(chunk) });
|
||||||
}
|
}
|
||||||
@@ -2617,7 +2636,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queryPumpPromise = pump;
|
this.queryPumpPromise = pump;
|
||||||
pump.finally(() => {
|
void pump.finally(() => {
|
||||||
if (this.queryPumpPromise === pump) {
|
if (this.queryPumpPromise === pump) {
|
||||||
this.queryPumpPromise = null;
|
this.queryPumpPromise = null;
|
||||||
}
|
}
|
||||||
@@ -2965,7 +2984,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.subtype === "status") {
|
if (message.subtype === "status") {
|
||||||
const status = (message as { status?: string }).status;
|
const status = toObjectRecord(message)?.status;
|
||||||
if (status === "compacting") {
|
if (status === "compacting") {
|
||||||
this.compacting = true;
|
this.compacting = true;
|
||||||
events.push({
|
events.push({
|
||||||
@@ -2977,7 +2996,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.subtype === "compact_boundary") {
|
if (message.subtype === "compact_boundary") {
|
||||||
const compactMetadata = readCompactionMetadata(message as Record<string, unknown>);
|
const compactMetadata = readCompactionMetadata(message);
|
||||||
events.push({
|
events.push({
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
item: {
|
item: {
|
||||||
@@ -3140,12 +3159,12 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
threadStartedSessionId: string | null;
|
threadStartedSessionId: string | null;
|
||||||
notice: AgentTimelineItem | null;
|
notice: AgentTimelineItem | null;
|
||||||
} {
|
} {
|
||||||
const msg = message as unknown as {
|
const msgRecord = toObjectRecord(message) ?? {};
|
||||||
session_id?: unknown;
|
const sessionId = extractSessionIdRaw({
|
||||||
sessionId?: unknown;
|
session_id: msgRecord.session_id,
|
||||||
session?: { id?: unknown } | null;
|
sessionId: msgRecord.sessionId,
|
||||||
};
|
session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
|
||||||
const sessionId = extractSessionIdRaw(msg).trim();
|
}).trim();
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return { threadStartedSessionId: null, notice: null };
|
return { threadStartedSessionId: null, notice: null };
|
||||||
}
|
}
|
||||||
@@ -3181,12 +3200,12 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
return { threadStartedSessionId: null, notice: null };
|
return { threadStartedSessionId: null, notice: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = message as unknown as {
|
const msgRecord = toObjectRecord(message) ?? {};
|
||||||
session_id?: unknown;
|
const newSessionId = extractSessionIdRaw({
|
||||||
sessionId?: unknown;
|
session_id: msgRecord.session_id,
|
||||||
session?: { id?: unknown } | null;
|
sessionId: msgRecord.sessionId,
|
||||||
};
|
session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
|
||||||
const newSessionId = extractSessionIdRaw(msg).trim();
|
}).trim();
|
||||||
if (!newSessionId) {
|
if (!newSessionId) {
|
||||||
return { threadStartedSessionId: null, notice: null };
|
return { threadStartedSessionId: null, notice: null };
|
||||||
}
|
}
|
||||||
@@ -3319,10 +3338,10 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private trackStreamEventUsage(event: unknown): AgentStreamEvent | null {
|
private trackStreamEventUsage(event: unknown): AgentStreamEvent | null {
|
||||||
if (!event || typeof event !== "object") {
|
const streamEvent = toObjectRecord(event);
|
||||||
|
if (!streamEvent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const streamEvent = event as Record<string, unknown>;
|
|
||||||
const eventType = readTrimmedString(streamEvent.type);
|
const eventType = readTrimmedString(streamEvent.type);
|
||||||
if (eventType === "message_start") {
|
if (eventType === "message_start") {
|
||||||
const inputTokens = readStreamRequestInputTokens(streamEvent);
|
const inputTokens = readStreamRequestInputTokens(streamEvent);
|
||||||
@@ -3538,7 +3557,12 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
|
|
||||||
let entry: Record<string, unknown>;
|
let entry: Record<string, unknown>;
|
||||||
try {
|
try {
|
||||||
entry = JSON.parse(trimmed) as Record<string, unknown>;
|
const parsed: unknown = JSON.parse(trimmed);
|
||||||
|
const record = toObjectRecord(parsed);
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entry = record;
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4202,15 +4226,21 @@ function hasToolLikeBlock(block?: ClaudeContentChunk | null): boolean {
|
|||||||
return type.includes("tool");
|
return type.includes("tool");
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCompactionMetadata(
|
function readCompactionMetadata(source: unknown): { trigger?: string; preTokens?: number } | null {
|
||||||
source: Record<string, unknown>,
|
const sourceRecord = toObjectRecord(source);
|
||||||
): { trigger?: string; preTokens?: number } | null {
|
if (!sourceRecord) {
|
||||||
const candidates = [source.compact_metadata, source.compactMetadata, source.compactionMetadata];
|
return null;
|
||||||
|
}
|
||||||
|
const candidates = [
|
||||||
|
sourceRecord.compact_metadata,
|
||||||
|
sourceRecord.compactMetadata,
|
||||||
|
sourceRecord.compactionMetadata,
|
||||||
|
];
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
if (!candidate || typeof candidate !== "object") {
|
const metadata = toObjectRecord(candidate);
|
||||||
|
if (!metadata) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const metadata = candidate as Record<string, unknown>;
|
|
||||||
const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
|
const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
|
||||||
const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
|
const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
|
||||||
const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
|
const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
|
||||||
@@ -4244,7 +4274,7 @@ function convertClaudeHistoryEntryPreamble(
|
|||||||
entry: ClaudeHistoryEntry,
|
entry: ClaudeHistoryEntry,
|
||||||
): { shortCircuit: AgentTimelineItem[] } | { proceed: { content: unknown } } {
|
): { shortCircuit: AgentTimelineItem[] } | { proceed: { content: unknown } } {
|
||||||
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
||||||
const compactMetadata = readCompactionMetadata(entry as Record<string, unknown>);
|
const compactMetadata = readCompactionMetadata(entry);
|
||||||
return {
|
return {
|
||||||
shortCircuit: [
|
shortCircuit: [
|
||||||
{
|
{
|
||||||
@@ -4459,10 +4489,10 @@ function applyClaudeSessionEntryToAccumulator(
|
|||||||
entryRaw: unknown,
|
entryRaw: unknown,
|
||||||
acc: ClaudeSessionDescriptorAccumulator,
|
acc: ClaudeSessionDescriptorAccumulator,
|
||||||
): void {
|
): void {
|
||||||
if (!entryRaw || typeof entryRaw !== "object") {
|
const entry = toObjectRecord(entryRaw);
|
||||||
|
if (!entry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const entry = entryRaw as Record<string, unknown> & { message?: unknown };
|
|
||||||
if (entry.isSidechain) {
|
if (entry.isSidechain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4554,10 +4584,10 @@ async function parseClaudeSessionDescriptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractClaudeUserText(messageRaw: unknown): string | null {
|
function extractClaudeUserText(messageRaw: unknown): string | null {
|
||||||
if (!messageRaw || typeof messageRaw !== "object") {
|
const message = toObjectRecord(messageRaw);
|
||||||
|
if (!message) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const message = messageRaw as Record<string, unknown>;
|
|
||||||
if (typeof message.content === "string") {
|
if (typeof message.content === "string") {
|
||||||
const normalized = message.content.trim();
|
const normalized = message.content.trim();
|
||||||
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
||||||
@@ -4566,14 +4596,11 @@ function extractClaudeUserText(messageRaw: unknown): string | null {
|
|||||||
const normalized = message.text.trim();
|
const normalized = message.text.trim();
|
||||||
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
||||||
}
|
}
|
||||||
if (Array.isArray(message.content)) {
|
if (isUnknownArray(message.content)) {
|
||||||
for (const block of message.content) {
|
for (const block of message.content) {
|
||||||
if (
|
const blockRecord = toObjectRecord(block);
|
||||||
block &&
|
if (blockRecord && typeof blockRecord.text === "string") {
|
||||||
typeof block === "object" &&
|
const normalized = blockRecord.text.trim();
|
||||||
typeof (block as { text?: unknown }).text === "string"
|
|
||||||
) {
|
|
||||||
const normalized = (block as { text: string }).text.trim();
|
|
||||||
if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
|
if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user