Skip to content

Commit 83f7203

Browse files
committed
refactor(normalization): remove record coercion facades
1 parent bf5a108 commit 83f7203

8 files changed

Lines changed: 31 additions & 33 deletions

File tree

src/gateway/server-methods/doctor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// cron state, and REM harness previews for operator diagnostics.
33
import fs from "node:fs/promises";
44
import path from "node:path";
5+
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
56
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
67
import type { OpenClawConfig } from "../../config/types.openclaw.js";
78
import {
@@ -25,7 +26,7 @@ import {
2526
repairDreamingArtifacts,
2627
writeBackfillDiaryEntries,
2728
} from "./doctor.memory-core-runtime.js";
28-
import { asRecord, normalizeTrimmedString } from "./record-shared.js";
29+
import { normalizeTrimmedString } from "./record-shared.js";
2930
import type { GatewayRequestHandlers } from "./types.js";
3031

3132
const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion";
@@ -932,7 +933,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
932933
const cfg = context.getRuntimeConfig();
933934
const agentId = resolveDefaultAgentId(cfg);
934935
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
935-
const req = asRecord(params);
936+
const req = asOptionalRecord(params);
936937
const grounded = Boolean(req?.grounded);
937938
const includePromoted = Boolean(req?.includePromoted);
938939
const requestedLimit =

src/gateway/server-methods/record-shared.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/**
22
* Small normalization helpers shared by gateway request handlers.
33
*/
4-
export { asOptionalRecord as asRecord } from "../../../packages/normalization-core/src/record-coerce.js";
5-
64
/** Returns a non-empty trimmed string, or `undefined` for non-string input. */
75
export function normalizeTrimmedString(value: unknown): string | undefined {
86
if (typeof value !== "string") {

src/gateway/server-methods/sessions-files.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Gateway methods expose files referenced by one session transcript.
22
import path from "node:path";
3+
import { asOptionalObjectRecord } from "@openclaw/normalization-core/record-coerce";
34
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
45
import {
56
ErrorCodes,
@@ -60,10 +61,6 @@ function sessionFilesError(type: string, message: string, details?: Record<strin
6061
});
6162
}
6263

63-
function asRecord(value: unknown): Record<string, unknown> | undefined {
64-
return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
65-
}
66-
6764
function normalizePathValue(value: unknown): string | undefined {
6865
if (typeof value !== "string") {
6966
return undefined;
@@ -115,9 +112,9 @@ function addStructuredPatchFiles(files: Map<string, TouchedFile>, changes: unkno
115112
return;
116113
}
117114
for (const changeValue of changes) {
118-
const change = asRecord(changeValue);
115+
const change = asOptionalObjectRecord(changeValue);
119116
addTouchedFile(files, normalizePathValue(change?.path), "modified");
120-
const kind = asRecord(change?.kind);
117+
const kind = asOptionalObjectRecord(change?.kind);
121118
addTouchedFile(
122119
files,
123120
normalizePathValue(kind?.move_path) ?? normalizePathValue(kind?.movePath),
@@ -140,17 +137,20 @@ function isToolCallBlockType(value: unknown): boolean {
140137
}
141138

142139
function collectTouchedFilesFromMessage(message: unknown, files: Map<string, TouchedFile>) {
143-
const record = asRecord(message);
140+
const record = asOptionalObjectRecord(message);
144141
if (record?.role !== "assistant" || !Array.isArray(record.content)) {
145142
return;
146143
}
147144
for (const blockValue of record.content) {
148-
const block = asRecord(blockValue);
145+
const block = asOptionalObjectRecord(blockValue);
149146
if (!block || !isToolCallBlockType(block.type)) {
150147
continue;
151148
}
152149
const toolName = normalizeOptionalString(block.name)?.toLowerCase();
153-
const args = asRecord(block.arguments) ?? asRecord(block.input) ?? asRecord(block.args);
150+
const args =
151+
asOptionalObjectRecord(block.arguments) ??
152+
asOptionalObjectRecord(block.input) ??
153+
asOptionalObjectRecord(block.args);
154154
if (!toolName || !args) {
155155
continue;
156156
}

src/gateway/server-methods/talk-shared.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Talk shared helpers build provider configs, launch options, tool schemas, and
22
// room event broadcasts used by browser and gateway-owned Talk sessions.
3+
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
34
import {
45
normalizeLowercaseStringOrEmpty,
56
normalizeOptionalLowercaseString,
@@ -25,7 +26,6 @@ import type {
2526
import type { TalkEvent } from "../../talk/talk-events.js";
2627
import { ADMIN_SCOPE } from "../operator-scopes.js";
2728
import type { TalkHandoffTurnResult } from "../talk-handoff.js";
28-
import { asRecord } from "./record-shared.js";
2929

3030
export function canUseTalkDirectTools(client: { connect?: { scopes?: string[] } } | null): boolean {
3131
const scopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : [];
@@ -66,7 +66,7 @@ export function talkHandoffErrorCode(reason: TalkHandoffFailureReason) {
6666
}
6767

6868
function getRecord(value: unknown): Record<string, unknown> | undefined {
69-
return asRecord(value) ?? undefined;
69+
return asOptionalRecord(value) ?? undefined;
7070
}
7171

7272
function singleRecordKey(record: Record<string, unknown> | undefined): string | undefined {

src/gateway/server-methods/talk.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Gateway RPC handlers for Talk voice, transcription, and speech synthesis surfaces.
2+
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
23
import {
34
normalizeLowercaseStringOrEmpty,
45
normalizeOptionalLowercaseString,
@@ -45,7 +46,6 @@ import {
4546
} from "../../tts/tts.js";
4647
import { ADMIN_SCOPE, TALK_SECRETS_SCOPE } from "../operator-scopes.js";
4748
import { formatForLog } from "../ws-log.js";
48-
import { asRecord } from "./record-shared.js";
4949
import { talkClientHandlers } from "./talk-client.js";
5050
import { talkSessionHandlers } from "./talk-session.js";
5151
import {
@@ -72,7 +72,7 @@ function canReadTalkSecrets(client: { connect?: { scopes?: string[] } } | null):
7272
}
7373

7474
function asStringRecord(value: unknown): Record<string, string> | undefined {
75-
const record = asRecord(value);
75+
const record = asOptionalRecord(value);
7676
if (!record) {
7777
return undefined;
7878
}
@@ -113,20 +113,20 @@ function withTalkBaseTtsSpeakerSelectionCompat(
113113
baseTts: Record<string, unknown>,
114114
): Record<string, unknown> {
115115
const next = withSpeakerSelectionCompat(baseTts);
116-
const providers = asRecord(baseTts.providers);
116+
const providers = asOptionalRecord(baseTts.providers);
117117
if (providers) {
118118
next.providers = Object.fromEntries(
119119
Object.entries(providers).map(([providerId, providerConfig]) => [
120120
providerId,
121-
withSpeakerSelectionCompat(asRecord(providerConfig) ?? {}),
121+
withSpeakerSelectionCompat(asOptionalRecord(providerConfig) ?? {}),
122122
]),
123123
);
124124
}
125125
for (const [key, value] of Object.entries(baseTts)) {
126126
if (key === "providers") {
127127
continue;
128128
}
129-
const record = asRecord(value);
129+
const record = asOptionalRecord(value);
130130
if (record) {
131131
next[key] = withSpeakerSelectionCompat(record);
132132
}
@@ -157,7 +157,7 @@ function buildTalkTtsConfig(
157157
}
158158

159159
const baseTts = withTalkBaseTtsSpeakerSelectionCompat(
160-
asRecord(config.messages?.tts) ?? {},
160+
asOptionalRecord(config.messages?.tts) ?? {},
161161
) as TtsConfig;
162162
const providerConfig = withSpeakerSelectionFallbackCompat(resolved.config);
163163
const resolvedProviderConfig =
@@ -172,7 +172,7 @@ function buildTalkTtsConfig(
172172
auto: "always",
173173
provider,
174174
providers: {
175-
...((asRecord(baseTts.providers) ?? {}) as TtsProviderConfigMap),
175+
...((asOptionalRecord(baseTts.providers) ?? {}) as TtsProviderConfigMap),
176176
[provider]: resolvedProviderConfig,
177177
},
178178
};
@@ -425,10 +425,10 @@ function resolveTalkResponseFromConfig(params: {
425425

426426
const speechProvider = getSpeechProvider(provider, params.runtimeConfig);
427427
const sourceBaseTts = withTalkBaseTtsSpeakerSelectionCompat(
428-
asRecord(params.sourceConfig.messages?.tts) ?? {},
428+
asOptionalRecord(params.sourceConfig.messages?.tts) ?? {},
429429
);
430430
const runtimeBaseTts = withTalkBaseTtsSpeakerSelectionCompat(
431-
asRecord(params.runtimeConfig.messages?.tts) ?? {},
431+
asOptionalRecord(params.runtimeConfig.messages?.tts) ?? {},
432432
);
433433
const sourceProviderConfig = withSpeakerSelectionFallbackCompat(sourceResolved?.config);
434434
const runtimeProviderConfig = withSpeakerSelectionFallbackCompat(runtimeResolved?.config);
@@ -474,7 +474,7 @@ function stripUnresolvedSecretApiKey(config: TalkProviderConfig): TalkProviderCo
474474
function stripUnresolvedSecretApiKeysFromBaseTtsProviders(
475475
base: Record<string, unknown>,
476476
): Record<string, unknown> {
477-
const providers = asRecord(base.providers);
477+
const providers = asOptionalRecord(base.providers);
478478
if (!providers) {
479479
return base;
480480
}
@@ -486,7 +486,7 @@ function stripUnresolvedSecretApiKeysFromBaseTtsProviders(
486486
// they're already validated upstream.
487487
const cleaned: Record<string, unknown> = Object.create(null);
488488
for (const [providerId, providerConfig] of Object.entries(providers)) {
489-
const cfg = asRecord(providerConfig);
489+
const cfg = asOptionalRecord(providerConfig);
490490
if (!cfg) {
491491
cleaned[providerId] = providerConfig;
492492
continue;

src/talk/diagnostics.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
* The diagnostic stream needs timing and size counters for reliability work,
55
* but must not export raw provider payloads, transcripts, or audio content.
66
*/
7+
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
78
import {
89
emitTrustedDiagnosticEvent,
910
type DiagnosticEventInput,
1011
} from "../infra/diagnostic-events.js";
11-
import { firstFiniteTalkEventNumber, talkEventPayloadRecord } from "./event-metrics.js";
12+
import { firstFiniteTalkEventNumber } from "./event-metrics.js";
1213
import type { TalkEvent } from "./talk-events.js";
1314

1415
type TalkDiagnosticEventInput = Extract<DiagnosticEventInput, { type: "talk.event" }>;
1516

1617
/** Convert a Talk event into the bounded diagnostic payload shape. */
1718
export function createTalkDiagnosticEvent(event: TalkEvent): TalkDiagnosticEventInput {
18-
const payload = talkEventPayloadRecord(event.payload);
19+
const payload = asOptionalRecord(event.payload);
1920
return {
2021
type: "talk.event",
2122
sessionId: event.sessionId,

src/talk/event-metrics.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
* Talk event payloads are provider-owned JSON blobs, so callers must coerce
55
* records and read only bounded numeric counters that are safe to export.
66
*/
7-
/** Coerce unknown Talk event payloads into optional records for metric reads. */
8-
export { asOptionalRecord as talkEventPayloadRecord } from "../../packages/normalization-core/src/record-coerce.js";
9-
107
/** Read the first non-negative finite number from a provider payload record. */
118
export function firstFiniteTalkEventNumber(
129
record: Record<string, unknown> | undefined,

src/talk/logging.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Talk logging helpers write voice session logs and diagnostic entries.
2+
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
23
import { getChildLogger } from "../logging/logger.js";
3-
import { firstFiniteTalkEventNumber, talkEventPayloadRecord } from "./event-metrics.js";
4+
import { firstFiniteTalkEventNumber } from "./event-metrics.js";
45
import type { TalkEvent, TalkEventType } from "./talk-events.js";
56

67
/**
@@ -37,7 +38,7 @@ export function createTalkLogRecord(event: TalkEvent): TalkLogRecord | undefined
3738
return undefined;
3839
}
3940

40-
const payload = talkEventPayloadRecord(event.payload);
41+
const payload = asOptionalRecord(event.payload);
4142
const attributes: Record<string, string | number | boolean> = {
4243
sessionId: event.sessionId,
4344
talkEventType: event.type,

0 commit comments

Comments
 (0)