From 6c53dfa1df99ef82d8d25600f809b1296176c4e0 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 4 Jul 2026 12:15:57 -0700 Subject: [PATCH] refactor(infra): consolidate bounded HTTP body reads (#99744) * refactor(infra): consolidate bounded HTTP body reads * fix(plugin-sdk): preserve HTTP body export boundaries --- .../tsconfig.package-boundary.paths.json | 3 - extensions/xai/tsconfig.json | 3 - packages/media-core/package.json | 7 +- packages/media-core/src/index.ts | 3 +- .../src/read-response-with-limit.ts | 188 ----------------- scripts/lib/extension-package-boundary.ts | 3 - scripts/plugin-sdk-surface-report.mjs | 6 +- ...e-extension-package-boundary-artifacts.mjs | 2 - scripts/proof-telegram-bound.mjs | 6 +- security/opengrep/precise.yml | 3 - src/agents/anthropic-transport-stream.ts | 2 +- .../google-prompt-cache.ts | 2 +- .../openrouter-model-capabilities.ts | 2 +- src/agents/model-scan.ts | 8 +- src/agents/provider-http-errors.ts | 2 +- src/agents/runtime/proxy.ts | 2 +- src/cli/capability-cli.ts | 2 +- src/commands/docs.ts | 4 +- src/gateway/model-pricing-cache.ts | 2 +- src/infra/clawhub.ts | 5 +- .../infra/http-body.response.test.ts | 4 +- src/infra/http-body.ts | 191 +++++++++++++++++- src/infra/push-apns.relay.ts | 2 +- src/link-understanding/runner.ts | 4 +- src/llm/utils/oauth/anthropic.ts | 2 +- src/media/fetch.ts | 10 +- src/media/input-files.ts | 2 +- src/music-generation/provider-assets.ts | 2 +- src/plugin-sdk/infra-runtime.ts | 21 +- src/plugin-sdk/media-runtime.ts | 6 +- .../provider-catalog-live-runtime.ts | 2 +- src/plugin-sdk/response-limit-runtime.ts | 2 +- src/plugins/plugin-sdk-native-resolver.ts | 1 - src/plugins/provider-self-hosted-setup.ts | 4 +- src/plugins/sdk-alias.test.ts | 17 -- src/plugins/sdk-alias.ts | 7 - src/video-generation/dashscope-compatible.ts | 4 +- test/vitest/vitest.shared.config.ts | 1 - tsconfig.json | 3 - tsdown.config.ts | 1 - 40 files changed, 256 insertions(+), 285 deletions(-) delete mode 100644 packages/media-core/src/read-response-with-limit.ts rename packages/media-core/src/read-response-with-limit.test.ts => src/infra/http-body.response.test.ts (98%) diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json index cb159679ea6b..b06b52aad3cf 100644 --- a/extensions/tsconfig.package-boundary.paths.json +++ b/extensions/tsconfig.package-boundary.paths.json @@ -239,9 +239,6 @@ "@openclaw/media-core/read-byte-stream-with-limit": [ "../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts" ], - "@openclaw/media-core/read-response-with-limit": [ - "../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts" - ], "@openclaw/media-core/*": [ "../dist/plugin-sdk/packages/media-core/src/*.d.ts" ], diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json index cc875c6708f9..0caa51548cc9 100644 --- a/extensions/xai/tsconfig.json +++ b/extensions/xai/tsconfig.json @@ -225,9 +225,6 @@ "@openclaw/media-core/read-byte-stream-with-limit": [ "../../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts" ], - "@openclaw/media-core/read-response-with-limit": [ - "../../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts" - ], "@openclaw/media-core/*": [ "../../dist/plugin-sdk/packages/media-core/src/*.d.ts" ], diff --git a/packages/media-core/package.json b/packages/media-core/package.json index d813bfb2f61f..dcdfa581f0e9 100644 --- a/packages/media-core/package.json +++ b/packages/media-core/package.json @@ -58,11 +58,6 @@ "types": "./dist/read-byte-stream-with-limit.d.mts", "import": "./dist/read-byte-stream-with-limit.mjs", "default": "./dist/read-byte-stream-with-limit.mjs" - }, - "./read-response-with-limit": { - "types": "./dist/read-response-with-limit.d.mts", - "import": "./dist/read-response-with-limit.mjs", - "default": "./dist/read-response-with-limit.mjs" } }, "dependencies": { @@ -70,6 +65,6 @@ "file-type": "22.0.1" }, "scripts": { - "build": "tsdown src/index.ts src/base64.ts src/constants.ts src/content-length.ts src/file-name.ts src/inbound-path-policy.ts src/inline-image-data-url.ts src/media-source-url.ts src/mime.ts src/read-byte-stream-with-limit.ts src/read-response-with-limit.ts --no-config --platform node --format esm --dts --out-dir dist --clean" + "build": "tsdown src/index.ts src/base64.ts src/constants.ts src/content-length.ts src/file-name.ts src/inbound-path-policy.ts src/inline-image-data-url.ts src/media-source-url.ts src/mime.ts src/read-byte-stream-with-limit.ts --no-config --platform node --format esm --dts --out-dir dist --clean" } } diff --git a/packages/media-core/src/index.ts b/packages/media-core/src/index.ts index 8c0ef4b106db..16efe6ee0c15 100644 --- a/packages/media-core/src/index.ts +++ b/packages/media-core/src/index.ts @@ -1,4 +1,4 @@ -// Public barrel for media URL, MIME, path, and bounded-read helpers. +// Public barrel for media URL, MIME, path, and byte-stream helpers. export * from "./base64.js"; export * from "./constants.js"; @@ -9,4 +9,3 @@ export * from "./inline-image-data-url.js"; export * from "./media-source-url.js"; export * from "./mime.js"; export * from "./read-byte-stream-with-limit.js"; -export * from "./read-response-with-limit.js"; diff --git a/packages/media-core/src/read-response-with-limit.ts b/packages/media-core/src/read-response-with-limit.ts deleted file mode 100644 index 73028e00dfe2..000000000000 --- a/packages/media-core/src/read-response-with-limit.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Media Core module implements read response with limit behavior. -import { toErrorObject } from "@openclaw/normalization-core/error-coercion"; -import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; - -/** Reads one chunk, rejecting and cancelling the reader after an idle timeout. */ -export async function readChunkWithIdleTimeout( - reader: ReadableStreamDefaultReader, - chunkTimeoutMs: number, - onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error, -): Promise>> { - let timeoutId: ReturnType | undefined; - let timedOut = false; - - return await new Promise((resolve, reject) => { - const clear = () => { - if (timeoutId !== undefined) { - clearTimeout(timeoutId); - timeoutId = undefined; - } - }; - - const resolvedChunkTimeoutMs = resolveTimerTimeoutMs(chunkTimeoutMs, 1); - timeoutId = setTimeout(() => { - timedOut = true; - const error = - onIdleTimeout?.({ chunkTimeoutMs: resolvedChunkTimeoutMs }) ?? - new Error(`Media download stalled: no data received for ${resolvedChunkTimeoutMs}ms`); - clear(); - // Cancel the body with the same error so fetch-backed streams release - // sockets/buffers instead of idling after the caller times out. - void reader.cancel(error).catch(() => undefined); - reject(error); - }, resolvedChunkTimeoutMs); - - void reader.read().then( - (result) => { - clear(); - if (!timedOut) { - resolve(result); - } - }, - (err: unknown) => { - clear(); - if (!timedOut) { - reject(toErrorObject(err, "Non-Error rejection")); - } - }, - ); - }); -} - -type ReadResponsePrefixResult = { - buffer: Buffer; - size: number; - truncated: boolean; -}; - -async function readResponsePrefix( - res: Response, - maxBytes: number, - opts?: { - chunkTimeoutMs?: number; - onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; - }, -): Promise { - const chunkTimeoutMs = opts?.chunkTimeoutMs; - const body = res.body; - if (!body || typeof body.getReader !== "function") { - const fallback = Buffer.from(await res.arrayBuffer()); - if (fallback.length > maxBytes) { - return { - buffer: fallback.subarray(0, maxBytes), - size: fallback.length, - truncated: true, - }; - } - return { buffer: fallback, size: fallback.length, truncated: false }; - } - - const reader = body.getReader(); - const chunks: Uint8Array[] = []; - let total = 0; - let size = 0; - let truncated = false; - try { - while (true) { - const { done, value } = chunkTimeoutMs - ? await readChunkWithIdleTimeout(reader, chunkTimeoutMs, opts?.onIdleTimeout) - : await reader.read(); - if (done) { - size = total; - break; - } - if (!value?.length) { - continue; - } - const nextTotal = total + value.length; - if (nextTotal > maxBytes) { - const remaining = maxBytes - total; - if (remaining > 0) { - chunks.push(value.subarray(0, remaining)); - total += remaining; - } - size = nextTotal; - truncated = true; - try { - await reader.cancel(); - } catch {} - break; - } - chunks.push(value); - total = nextTotal; - size = total; - } - } finally { - try { - reader.releaseLock(); - } catch {} - } - - return { - buffer: Buffer.concat( - chunks.map((chunk) => Buffer.from(chunk)), - total, - ), - size, - truncated, - }; -} - -/** Reads a response body under a byte cap, cancelling the stream on overflow or idle timeout. */ -export async function readResponseWithLimit( - res: Response, - maxBytes: number, - opts?: { - onOverflow?: (params: { size: number; maxBytes: number; res: Response }) => Error; - chunkTimeoutMs?: number; - onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; - }, -): Promise { - const onOverflow = - opts?.onOverflow ?? - ((params: { size: number; maxBytes: number }) => - new Error(`Content too large: ${params.size} bytes (limit: ${params.maxBytes} bytes)`)); - const prefix = await readResponsePrefix(res, maxBytes, { - chunkTimeoutMs: opts?.chunkTimeoutMs, - onIdleTimeout: opts?.onIdleTimeout, - }); - if (prefix.truncated) { - throw onOverflow({ size: prefix.size, maxBytes, res }); - } - return prefix.buffer; -} - -/** Reads a small collapsed text prefix from a response body for diagnostics/errors. */ -export async function readResponseTextSnippet( - res: Response, - opts?: { - maxBytes?: number; - maxChars?: number; - chunkTimeoutMs?: number; - onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; - }, -): Promise { - const maxBytes = opts?.maxBytes ?? 8 * 1024; - const maxChars = opts?.maxChars ?? 200; - const prefix = await readResponsePrefix(res, maxBytes, { - chunkTimeoutMs: opts?.chunkTimeoutMs, - onIdleTimeout: opts?.onIdleTimeout, - }); - if (prefix.buffer.length === 0) { - return undefined; - } - - const text = new TextDecoder().decode(prefix.buffer); - if (!text) { - return undefined; - } - - const collapsed = text.replace(/\s+/g, " ").trim(); - if (!collapsed) { - return undefined; - } - if (collapsed.length > maxChars) { - return `${collapsed.slice(0, maxChars)}…`; - } - return prefix.truncated ? `${collapsed}…` : collapsed; -} diff --git a/scripts/lib/extension-package-boundary.ts b/scripts/lib/extension-package-boundary.ts index 3e9b23e5b593..40a854522b97 100644 --- a/scripts/lib/extension-package-boundary.ts +++ b/scripts/lib/extension-package-boundary.ts @@ -172,9 +172,6 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { "@openclaw/media-core/read-byte-stream-with-limit": [ "../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts", ], - "@openclaw/media-core/read-response-with-limit": [ - "../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts", - ], "@openclaw/media-core/*": ["../dist/plugin-sdk/packages/media-core/src/*.d.ts"], "@openclaw/normalization-core/record-coerce": [ "../dist/plugin-sdk/packages/normalization-core/src/record-coerce.d.ts", diff --git a/scripts/plugin-sdk-surface-report.mjs b/scripts/plugin-sdk-surface-report.mjs index a1c2d49d4f57..631169de89f8 100644 --- a/scripts/plugin-sdk-surface-report.mjs +++ b/scripts/plugin-sdk-surface-report.mjs @@ -202,15 +202,15 @@ let publicDeprecatedExportsByEntrypointBudget; try { budgets = { publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 323), - publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10422), - publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5233), + publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10425), + publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5237), publicDeprecatedExports: readBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS", 3261, ), publicWildcardReexports: readBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_WILDCARD_REEXPORTS", - 214, + 212, ), }; publicDeprecatedExportsByEntrypointBudget = readEntrypointBudgetEnv( diff --git a/scripts/prepare-extension-package-boundary-artifacts.mjs b/scripts/prepare-extension-package-boundary-artifacts.mjs index 8d0bb85bab00..8bf7727af2e0 100644 --- a/scripts/prepare-extension-package-boundary-artifacts.mjs +++ b/scripts/prepare-extension-package-boundary-artifacts.mjs @@ -109,7 +109,6 @@ const ROOT_DTS_REQUIRED_OUTPUTS = [ "dist/plugin-sdk/packages/media-core/src/media-source-url.d.ts", "dist/plugin-sdk/packages/media-core/src/mime.d.ts", "dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts", - "dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts", ...ACP_CORE_REQUIRED_DTS_OUTPUTS, "dist/plugin-sdk/packages/terminal-core/src/ansi.d.ts", "dist/plugin-sdk/packages/terminal-core/src/decorative-emoji.d.ts", @@ -171,7 +170,6 @@ const PACKAGE_DTS_REQUIRED_OUTPUTS = [ "packages/plugin-sdk/dist/packages/media-core/src/media-source-url.d.ts", "packages/plugin-sdk/dist/packages/media-core/src/mime.d.ts", "packages/plugin-sdk/dist/packages/media-core/src/read-byte-stream-with-limit.d.ts", - "packages/plugin-sdk/dist/packages/media-core/src/read-response-with-limit.d.ts", ...ACP_CORE_REQUIRED_PACKAGE_DTS_OUTPUTS, "packages/plugin-sdk/dist/packages/model-catalog-core/src/configured-model-refs.d.ts", "packages/plugin-sdk/dist/packages/model-catalog-core/src/model-catalog-normalize.d.ts", diff --git a/scripts/proof-telegram-bound.mjs b/scripts/proof-telegram-bound.mjs index 51126fa4043d..da30c29a1307 100644 --- a/scripts/proof-telegram-bound.mjs +++ b/scripts/proof-telegram-bound.mjs @@ -5,9 +5,9 @@ import { resolve } from "node:path"; const pkgRoot = resolve(import.meta.dirname, ".."); -const { readResponseWithLimit } = await import( - `${pkgRoot}/packages/media-core/src/read-response-with-limit.ts` -).catch(() => import("@openclaw/media-core/read-response-with-limit")); +const { readResponseWithLimit } = await import(`${pkgRoot}/src/infra/http-body.ts`).catch( + () => import("openclaw/plugin-sdk/response-limit-runtime"), +); const CAP = 1 * 1024 * 1024; // 1 MiB proof cap const STREAM_SIZE = 24 * 1024 * 1024; // 24 MiB – simulates hostile oversized Bot API response diff --git a/security/opengrep/precise.yml b/security/opengrep/precise.yml index ec3ee07fc7fa..1f1700650126 100644 --- a/security/opengrep/precise.yml +++ b/security/opengrep/precise.yml @@ -3423,9 +3423,6 @@ rules: - src/plugins/web-fetch-providers*.ts - src/media/** - src/infra/net/** - exclude: - - src/media/read-response-with-limit.ts - - src/media/read-response-with-limit.test.ts patterns: - pattern-either: - pattern: $RESP.text() diff --git a/src/agents/anthropic-transport-stream.ts b/src/agents/anthropic-transport-stream.ts index ffa8b5d9ddc2..e8479d229acd 100644 --- a/src/agents/anthropic-transport-stream.ts +++ b/src/agents/anthropic-transport-stream.ts @@ -3,10 +3,10 @@ * Converts OpenClaw contexts/tools into Anthropic payloads, streams SSE events * back into runtime output blocks, and applies provider request policy. */ -import { readResponseTextSnippet } from "@openclaw/media-core/read-response-with-limit"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { createAbortError as createNamedAbortError } from "../infra/abort-signal.js"; import { toErrorObject } from "../infra/errors.js"; +import { readResponseTextSnippet } from "../infra/http-body.js"; import { getEnvApiKey } from "../llm/env-api-keys.js"; import { calculateCost, clampThinkingLevel } from "../llm/model-utils.js"; import { diff --git a/src/agents/embedded-agent-runner/google-prompt-cache.ts b/src/agents/embedded-agent-runner/google-prompt-cache.ts index 278e87d1c6d8..3cc2f9bfcf26 100644 --- a/src/agents/embedded-agent-runner/google-prompt-cache.ts +++ b/src/agents/embedded-agent-runner/google-prompt-cache.ts @@ -2,7 +2,6 @@ * Prepares Google prompt-cache payloads for embedded-agent stream calls. */ import crypto from "node:crypto"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { asDateTimestampMs, isFutureDateTimestampMs, @@ -11,6 +10,7 @@ import { import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { parseGeminiAuth } from "../../infra/gemini-auth.js"; import { normalizeGoogleApiBaseUrl } from "../../infra/google-api-base-url.js"; +import { readResponseWithLimit } from "../../infra/http-body.js"; import { streamWithPayloadPatch } from "../../llm/providers/stream-wrappers/stream-payload-utils.js"; import type { Model } from "../../llm/types.js"; import { buildGuardedModelFetch } from "../provider-transport-fetch.js"; diff --git a/src/agents/embedded-agent-runner/openrouter-model-capabilities.ts b/src/agents/embedded-agent-runner/openrouter-model-capabilities.ts index ea7fed980ba9..6daf624b956e 100644 --- a/src/agents/embedded-agent-runner/openrouter-model-capabilities.ts +++ b/src/agents/embedded-agent-runner/openrouter-model-capabilities.ts @@ -18,8 +18,8 @@ * capabilities instead of the text-only fallback. */ -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { formatErrorMessage } from "../../infra/errors.js"; +import { readResponseWithLimit } from "../../infra/http-body.js"; import { resolveProxyFetchFromEnv } from "../../infra/net/proxy-fetch.js"; import { parseStrictFiniteNumber } from "../../infra/parse-finite-number.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; diff --git a/src/agents/model-scan.ts b/src/agents/model-scan.ts index 32c7794236e2..f7b85a1e72b6 100644 --- a/src/agents/model-scan.ts +++ b/src/agents/model-scan.ts @@ -1,7 +1,3 @@ -/** - * Scans remote provider model catalogs for configured providers. - */ -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { asDateTimestampMs, @@ -17,6 +13,10 @@ import { } from "@openclaw/normalization-core/string-normalization"; import { Type } from "typebox"; import { formatErrorMessage } from "../infra/errors.js"; +/** + * Scans remote provider model catalogs for configured providers. + */ +import { readResponseWithLimit } from "../infra/http-body.js"; import { getEnvApiKey } from "../llm/env-api-keys.js"; import type { OpenAICompletionsOptions } from "../llm/providers/openai-completions.js"; import { complete } from "../llm/stream.js"; diff --git a/src/agents/provider-http-errors.ts b/src/agents/provider-http-errors.ts index 18fe82eb712d..a052d25c5b2b 100644 --- a/src/agents/provider-http-errors.ts +++ b/src/agents/provider-http-errors.ts @@ -5,8 +5,8 @@ * request ids, and binary payload guardrails into stable OpenClaw error shapes. */ export { asFiniteNumber } from "../../packages/normalization-core/src/number-coercion.js"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeOptionalString as trimToUndefined } from "../../packages/normalization-core/src/string-coerce.js"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { redactSensitiveText } from "../logging/redact.js"; export { asBoolean } from "../utils/boolean.js"; export { normalizeOptionalString as trimToUndefined } from "../../packages/normalization-core/src/string-coerce.js"; diff --git a/src/agents/runtime/proxy.ts b/src/agents/runtime/proxy.ts index 396c371e52b1..52d8a1c879a9 100644 --- a/src/agents/runtime/proxy.ts +++ b/src/agents/runtime/proxy.ts @@ -3,8 +3,8 @@ * The server manages auth and proxies requests to LLM providers. */ -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { resolvePositiveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; +import { readResponseWithLimit } from "../../infra/http-body.js"; // Internal import for JSON parsing utility import type { AssistantMessage, diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index a930d414e609..df7f3b9bc470 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -6,7 +6,6 @@ import path from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; import { detectMime, extensionForMime, normalizeMimeType } from "@openclaw/media-core/mime"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -56,6 +55,7 @@ import type { ImageGenerationOutputFormat, ImageGenerationQuality, } from "../image-generation/types.js"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { parseStrictFiniteNumber, parseStrictPositiveInteger, diff --git a/src/commands/docs.ts b/src/commands/docs.ts index fec7b94984b1..2f09a1299fd7 100644 --- a/src/commands/docs.ts +++ b/src/commands/docs.ts @@ -1,8 +1,8 @@ -// Implements docs link/search output for `openclaw docs`. -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { formatDocsLink } from "../../packages/terminal-core/src/links.js"; import { isRich, theme } from "../../packages/terminal-core/src/theme.js"; import { formatCliCommand } from "../cli/command-format.js"; +// Implements docs link/search output for `openclaw docs`. +import { readResponseWithLimit } from "../infra/http-body.js"; import type { RuntimeEnv } from "../runtime.js"; const SEARCH_API = "https://docs.openclaw.ai/api/search"; diff --git a/src/gateway/model-pricing-cache.ts b/src/gateway/model-pricing-cache.ts index cbbd9ff59f40..03e408b661fd 100644 --- a/src/gateway/model-pricing-cache.ts +++ b/src/gateway/model-pricing-cache.ts @@ -1,7 +1,6 @@ // Gateway model-pricing refresh and normalization. // Fetches, normalizes, and schedules cached pricing for model usage estimates. import type { ModelCatalogCost } from "@openclaw/model-catalog-core/model-catalog-types"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeOptionalString, resolvePrimaryStringValue, @@ -18,6 +17,7 @@ import { import { resolvePluginWebSearchConfig } from "../config/plugin-web-search-config.js"; import type { ModelDefinitionConfig } from "../config/types.models.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { planManifestModelCatalogRows } from "../model-catalog/index.js"; import { isInstalledPluginEnabled } from "../plugins/installed-plugin-index.js"; diff --git a/src/infra/clawhub.ts b/src/infra/clawhub.ts index 04202bf06e44..90ca37774a9f 100644 --- a/src/infra/clawhub.ts +++ b/src/infra/clawhub.ts @@ -3,10 +3,6 @@ import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { - readResponseTextSnippet, - readResponseWithLimit, -} from "@openclaw/media-core/read-response-with-limit"; import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { normalizeLowercaseStringOrEmpty, @@ -14,6 +10,7 @@ import { } from "@openclaw/normalization-core/string-coerce"; import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; import { sha256Base64, sha256Hex as digestSha256Hex } from "./crypto-digest.js"; +import { readResponseTextSnippet, readResponseWithLimit } from "./http-body.js"; import { parseStrictPositiveInteger } from "./parse-finite-number.js"; import { isAtLeast, parseSemver } from "./runtime-guard.js"; import { compareComparableSemver, parseComparableSemver } from "./semver-compare.js"; diff --git a/packages/media-core/src/read-response-with-limit.test.ts b/src/infra/http-body.response.test.ts similarity index 98% rename from packages/media-core/src/read-response-with-limit.test.ts rename to src/infra/http-body.response.test.ts index b53d62345ca2..e4258a044772 100644 --- a/packages/media-core/src/read-response-with-limit.test.ts +++ b/src/infra/http-body.response.test.ts @@ -1,7 +1,7 @@ -// Media Core tests cover read response with limit behavior. +// Tests bounded HTTP response reads and cleanup behavior. import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { readResponseTextSnippet, readResponseWithLimit } from "./read-response-with-limit.js"; +import { readResponseTextSnippet, readResponseWithLimit } from "./http-body.js"; function makeStream(chunks: Uint8Array[], delayMs?: number) { return new ReadableStream({ diff --git a/src/infra/http-body.ts b/src/infra/http-body.ts index 71234b2b74e0..f160e9df7c33 100644 --- a/src/infra/http-body.ts +++ b/src/infra/http-body.ts @@ -1,6 +1,7 @@ -// Reads HTTP request bodies with timeout and byte limits. +// Reads HTTP request and response bodies with timeout and byte limits. import type { IncomingMessage, ServerResponse } from "node:http"; import { clearTimeout as clearNodeTimeout, setTimeout as setNodeTimeout } from "node:timers"; +import { toErrorObject } from "@openclaw/normalization-core/error-coercion"; import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { formatErrorMessage } from "./errors.js"; import { parseStrictNonNegativeInteger } from "./parse-finite-number.js"; @@ -126,6 +127,194 @@ function advanceRequestBodyChunk( }; } +/** Reads one chunk, rejecting and cancelling the reader after an idle timeout. */ +export async function readChunkWithIdleTimeout( + reader: ReadableStreamDefaultReader, + chunkTimeoutMs: number, + onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error, +): Promise>> { + let timeoutId: ReturnType | undefined; + let timedOut = false; + + return await new Promise((resolve, reject) => { + const clear = () => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + timeoutId = undefined; + } + }; + + const resolvedChunkTimeoutMs = resolveTimerTimeoutMs(chunkTimeoutMs, 1); + timeoutId = setTimeout(() => { + timedOut = true; + const error = + onIdleTimeout?.({ chunkTimeoutMs: resolvedChunkTimeoutMs }) ?? + new Error(`Media download stalled: no data received for ${resolvedChunkTimeoutMs}ms`); + clear(); + // Cancel with the timeout error so fetch-backed streams release sockets + // and buffers instead of continuing after the caller has failed. + void reader.cancel(error).catch(() => undefined); + reject(error); + }, resolvedChunkTimeoutMs); + + void reader.read().then( + (result) => { + clear(); + if (!timedOut) { + resolve(result); + } + }, + (error: unknown) => { + clear(); + if (!timedOut) { + reject(toErrorObject(error, "Non-Error rejection")); + } + }, + ); + }); +} + +type ReadResponsePrefixResult = { + buffer: Buffer; + size: number; + truncated: boolean; +}; + +async function readResponsePrefix( + response: Response, + maxBytes: number, + options?: { + chunkTimeoutMs?: number; + onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; + }, +): Promise { + const body = response.body; + if (!body || typeof body.getReader !== "function") { + const fallback = Buffer.from(await response.arrayBuffer()); + if (fallback.length > maxBytes) { + return { + buffer: fallback.subarray(0, maxBytes), + size: fallback.length, + truncated: true, + }; + } + return { buffer: fallback, size: fallback.length, truncated: false }; + } + + const reader = body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + let size = 0; + let truncated = false; + try { + while (true) { + const { done, value } = options?.chunkTimeoutMs + ? await readChunkWithIdleTimeout( + reader, + options.chunkTimeoutMs, + options.onIdleTimeout, + ) + : await reader.read(); + if (done) { + size = total; + break; + } + if (!value?.length) { + continue; + } + const nextTotal = total + value.length; + if (nextTotal > maxBytes) { + const remaining = maxBytes - total; + if (remaining > 0) { + chunks.push(value.subarray(0, remaining)); + total += remaining; + } + size = nextTotal; + truncated = true; + try { + await reader.cancel(); + } catch {} + break; + } + chunks.push(value); + total = nextTotal; + size = total; + } + } finally { + try { + reader.releaseLock(); + } catch {} + } + + return { + buffer: Buffer.concat( + chunks.map((chunk) => Buffer.from(chunk)), + total, + ), + size, + truncated, + }; +} + +/** Reads a response body under a byte cap, cancelling the stream on overflow or idle timeout. */ +export async function readResponseWithLimit( + response: Response, + maxBytes: number, + options?: { + onOverflow?: (params: { size: number; maxBytes: number; res: Response }) => Error; + chunkTimeoutMs?: number; + onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; + }, +): Promise { + const onOverflow = + options?.onOverflow ?? + ((params: { size: number; maxBytes: number }) => + new Error(`Content too large: ${params.size} bytes (limit: ${params.maxBytes} bytes)`)); + const prefix = await readResponsePrefix(response, maxBytes, { + chunkTimeoutMs: options?.chunkTimeoutMs, + onIdleTimeout: options?.onIdleTimeout, + }); + if (prefix.truncated) { + throw onOverflow({ size: prefix.size, maxBytes, res: response }); + } + return prefix.buffer; +} + +/** Reads a small collapsed text prefix from a response body for diagnostics/errors. */ +export async function readResponseTextSnippet( + response: Response, + options?: { + maxBytes?: number; + maxChars?: number; + chunkTimeoutMs?: number; + onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error; + }, +): Promise { + const maxBytes = options?.maxBytes ?? 8 * 1024; + const maxChars = options?.maxChars ?? 200; + const prefix = await readResponsePrefix(response, maxBytes, { + chunkTimeoutMs: options?.chunkTimeoutMs, + onIdleTimeout: options?.onIdleTimeout, + }); + if (prefix.buffer.length === 0) { + return undefined; + } + + const text = new TextDecoder().decode(prefix.buffer); + if (!text) { + return undefined; + } + + const collapsed = text.replace(/\s+/g, " ").trim(); + if (!collapsed) { + return undefined; + } + if (collapsed.length > maxChars) { + return `${collapsed.slice(0, maxChars)}…`; + } + return prefix.truncated ? `${collapsed}…` : collapsed; +} + export async function readRequestBodyWithLimit( req: IncomingMessage, options: ReadRequestBodyOptions, diff --git a/src/infra/push-apns.relay.ts b/src/infra/push-apns.relay.ts index 8e3c469c41a9..6c94506a3c01 100644 --- a/src/infra/push-apns.relay.ts +++ b/src/infra/push-apns.relay.ts @@ -1,6 +1,5 @@ // Sends APNs notifications through the configured relay endpoint. import { URL } from "node:url"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { normalizeLowercaseStringOrEmpty, @@ -13,6 +12,7 @@ import { type DeviceIdentity, } from "./device-identity.js"; import { formatErrorMessage } from "./errors.js"; +import { readResponseWithLimit } from "./http-body.js"; import { normalizeHostname } from "./net/hostname.js"; type ApnsRelayPushType = "alert" | "background"; diff --git a/src/link-understanding/runner.ts b/src/link-understanding/runner.ts index 7bd670f534fc..24427a5d3195 100644 --- a/src/link-understanding/runner.ts +++ b/src/link-understanding/runner.ts @@ -1,10 +1,10 @@ -// Link-understanding runner fetches allowed URLs and invokes configured commands with bounded content. -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import type { MsgContext } from "../auto-reply/templating.js"; import { applyTemplate } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { LinkModelConfig, LinkToolsConfig } from "../config/types.tools.js"; import { logVerbose, shouldLogVerbose } from "../globals.js"; +// Link-understanding runner fetches allowed URLs and invokes configured commands with bounded content. +import { readResponseWithLimit } from "../infra/http-body.js"; import { fetchWithSsrFGuard, GUARDED_FETCH_MODE } from "../infra/net/fetch-guard.js"; import { CLI_OUTPUT_MAX_BUFFER } from "../media-understanding/defaults.js"; import { resolveTimeoutMs } from "../media-understanding/resolve.js"; diff --git a/src/llm/utils/oauth/anthropic.ts b/src/llm/utils/oauth/anthropic.ts index 3907c6da788a..a7b60efb019c 100644 --- a/src/llm/utils/oauth/anthropic.ts +++ b/src/llm/utils/oauth/anthropic.ts @@ -6,8 +6,8 @@ */ import type { Server } from "node:http"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { toErrorObject } from "../../../infra/errors.js"; +import { readResponseWithLimit } from "../../../infra/http-body.js"; import { generateOAuthState, generatePKCE, diff --git a/src/media/fetch.ts b/src/media/fetch.ts index 24c214848b53..d4659622d932 100644 --- a/src/media/fetch.ts +++ b/src/media/fetch.ts @@ -3,13 +3,13 @@ import { MAX_DOCUMENT_BYTES } from "@openclaw/media-core/constants"; import { parseMediaContentLength } from "@openclaw/media-core/content-length"; import { basenameFromAnyPath, extnameFromAnyPath } from "@openclaw/media-core/file-name"; import { detectMime, extensionForMime } from "@openclaw/media-core/mime"; -import { readChunkWithIdleTimeout } from "@openclaw/media-core/read-response-with-limit"; -import { - readResponseTextSnippet, - readResponseWithLimit, -} from "@openclaw/media-core/read-response-with-limit"; import { isAbortError } from "../infra/abort-signal.js"; import { formatErrorMessage } from "../infra/errors.js"; +import { + readChunkWithIdleTimeout, + readResponseTextSnippet, + readResponseWithLimit, +} from "../infra/http-body.js"; import { fetchWithSsrFGuard, withStrictGuardedFetchMode, diff --git a/src/media/input-files.ts b/src/media/input-files.ts index 8dd63844135a..ee4e5e5c2f05 100644 --- a/src/media/input-files.ts +++ b/src/media/input-files.ts @@ -2,12 +2,12 @@ import { canonicalizeBase64, estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; import { parseMediaContentLength } from "@openclaw/media-core/content-length"; import { detectMime } from "@openclaw/media-core/mime"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; import type { SsrFPolicy } from "../infra/net/ssrf.js"; import { logWarn } from "../logger.js"; diff --git a/src/music-generation/provider-assets.ts b/src/music-generation/provider-assets.ts index 509bc65a17c0..30fe2c39adc0 100644 --- a/src/music-generation/provider-assets.ts +++ b/src/music-generation/provider-assets.ts @@ -1,9 +1,9 @@ // Validates and normalizes provider asset attachments for music generation. import { maxBytesForKind } from "@openclaw/media-core/constants"; import { extensionForMime } from "@openclaw/media-core/mime"; -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { fetchProviderDownloadResponse } from "../media-understanding/shared.js"; import type { GeneratedMusicAsset } from "./types.js"; diff --git a/src/plugin-sdk/infra-runtime.ts b/src/plugin-sdk/infra-runtime.ts index 576eae83525e..1604714ded79 100644 --- a/src/plugin-sdk/infra-runtime.ts +++ b/src/plugin-sdk/infra-runtime.ts @@ -36,7 +36,26 @@ export * from "../infra/heartbeat-events.ts"; export * from "../infra/heartbeat-summary.ts"; export * from "../infra/heartbeat-visibility.ts"; export * from "../infra/home-dir.js"; -export * from "../infra/http-body.js"; +// Keep this deprecated barrel pinned to its shipped request-body surface; new +// response readers belong only to the focused response-limit/media entrypoints. +export { + __test__, + DEFAULT_WEBHOOK_BODY_TIMEOUT_MS, + DEFAULT_WEBHOOK_MAX_BODY_BYTES, + installRequestBodyLimitGuard, + isRequestBodyLimitError, + readJsonBodyWithLimit, + readRequestBodyWithLimit, + requestBodyErrorToText, + RequestBodyLimitError, + testApi, + type ReadJsonBodyOptions, + type ReadJsonBodyResult, + type ReadRequestBodyOptions, + type RequestBodyLimitErrorCode, + type RequestBodyLimitGuard, + type RequestBodyLimitGuardOptions, +} from "../infra/http-body.js"; export * from "../infra/json-files.js"; export * from "../infra/local-file-access.js"; export * from "../infra/map-size.js"; diff --git a/src/plugin-sdk/media-runtime.ts b/src/plugin-sdk/media-runtime.ts index f28b473c7f4a..c879b0402923 100644 --- a/src/plugin-sdk/media-runtime.ts +++ b/src/plugin-sdk/media-runtime.ts @@ -51,7 +51,11 @@ export * from "../media/png-encode.ts"; export * from "../media/qr-image.ts"; export * from "../media/qr-terminal.ts"; export * from "@openclaw/media-core/read-byte-stream-with-limit"; -export * from "@openclaw/media-core/read-response-with-limit"; +export { + readChunkWithIdleTimeout, + readResponseTextSnippet, + readResponseWithLimit, +} from "../infra/http-body.js"; export * from "../media/store.js"; export * from "../media/temp-files.js"; export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; diff --git a/src/plugin-sdk/provider-catalog-live-runtime.ts b/src/plugin-sdk/provider-catalog-live-runtime.ts index 3884026673da..fc08313b32fd 100644 --- a/src/plugin-sdk/provider-catalog-live-runtime.ts +++ b/src/plugin-sdk/provider-catalog-live-runtime.ts @@ -1,5 +1,5 @@ -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { isNonSecretApiKeyMarker } from "../agents/model-auth-markers.js"; +import { readResponseWithLimit } from "../infra/http-body.js"; import { retainSafeHeadersForCrossOriginRedirect } from "../infra/net/redirect-headers.js"; import { clearLiveCatalogCacheForTests, diff --git a/src/plugin-sdk/response-limit-runtime.ts b/src/plugin-sdk/response-limit-runtime.ts index 188d2e2f50bc..31c94c169442 100644 --- a/src/plugin-sdk/response-limit-runtime.ts +++ b/src/plugin-sdk/response-limit-runtime.ts @@ -1,4 +1,4 @@ // Narrow response-size reader for plugins that download bounded HTTP bodies. export { readByteStreamWithLimit } from "@openclaw/media-core/read-byte-stream-with-limit"; -export { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; +export { readResponseWithLimit } from "../infra/http-body.js"; diff --git a/src/plugins/plugin-sdk-native-resolver.ts b/src/plugins/plugin-sdk-native-resolver.ts index 6884064dc884..ba8b35d00038 100644 --- a/src/plugins/plugin-sdk-native-resolver.ts +++ b/src/plugins/plugin-sdk-native-resolver.ts @@ -78,7 +78,6 @@ const INTERNAL_CORE_PACKAGE_ALIASES = [ ["media-source-url", "media-source-url.ts"], ["mime", "mime.ts"], ["read-byte-stream-with-limit", "read-byte-stream-with-limit.ts"], - ["read-response-with-limit", "read-response-with-limit.ts"], ], }, { diff --git a/src/plugins/provider-self-hosted-setup.ts b/src/plugins/provider-self-hosted-setup.ts index fa6980086247..33066851932a 100644 --- a/src/plugins/provider-self-hosted-setup.ts +++ b/src/plugins/provider-self-hosted-setup.ts @@ -1,5 +1,3 @@ -// Builds setup metadata for self-hosted provider plugins. -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { findNormalizedProviderValue, normalizeProviderId, @@ -19,6 +17,8 @@ import { } from "../agents/self-hosted-provider-defaults.js"; import type { ModelDefinitionConfig } from "../config/types.models.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +// Builds setup metadata for self-hosted provider plugins. +import { readResponseWithLimit } from "../infra/http-body.js"; import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; import type { SsrFPolicy } from "../infra/net/ssrf.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 000ef17e691e..d686689f5bc5 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -1726,20 +1726,6 @@ describe("plugin sdk alias helpers", () => { srcFile: "catalog.ts", distFile: "catalog.mjs", }); - writeWorkspacePackageEntry({ - root: fixture.root, - packageDir: "media-core", - srcFile: "read-response-with-limit.ts", - distFile: "read-response-with-limit.mjs", - }); - const mediaCoreRootDistFile = path.join( - fixture.root, - "dist", - "media-core", - "read-response-with-limit.js", - ); - mkdirSafeDir(path.dirname(mediaCoreRootDistFile)); - fs.writeFileSync(mediaCoreRootDistFile, "export {};\n", "utf-8"); writeWorkspacePackageEntry({ root: fixture.root, packageDir: "acp-core", @@ -1812,9 +1798,6 @@ describe("plugin sdk alias helpers", () => { expect(fs.realpathSync(aliases["@openclaw/media-generation-core/catalog"] ?? "")).toBe( fs.realpathSync(mediaGenerationCore.distFile), ); - expect(fs.realpathSync(aliases["@openclaw/media-core/read-response-with-limit"] ?? "")).toBe( - fs.realpathSync(mediaCoreRootDistFile), - ); expect(fs.realpathSync(aliases["@openclaw/acp-core/normalize-text"] ?? "")).toBe( fs.realpathSync(acpCoreRootDistFile), ); diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index ddf1d3140ea0..21f8e90fc174 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -749,13 +749,6 @@ const WORKSPACE_PACKAGE_ALIAS_ENTRIES: WorkspacePackageAliasEntry[] = [ srcFile: "read-byte-stream-with-limit.ts", distFile: "read-byte-stream-with-limit.mjs", }, - { - packageName: "@openclaw/media-core", - packageDir: "media-core", - subpath: "read-response-with-limit", - srcFile: "read-response-with-limit.ts", - distFile: "read-response-with-limit.mjs", - }, { packageName: "@openclaw/normalization-core", packageDir: "normalization-core", diff --git a/src/video-generation/dashscope-compatible.ts b/src/video-generation/dashscope-compatible.ts index 6361fc533e7f..6f22f2ac3d11 100644 --- a/src/video-generation/dashscope-compatible.ts +++ b/src/video-generation/dashscope-compatible.ts @@ -1,5 +1,3 @@ -// DashScope-compatible video provider adapts DashScope-style generation APIs. -import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { @@ -14,6 +12,8 @@ import { waitProviderOperationPollInterval, type ProviderOperationTimeoutMs, } from "openclaw/plugin-sdk/provider-http"; +// DashScope-compatible video provider adapts DashScope-style generation APIs. +import { readResponseWithLimit } from "../infra/http-body.js"; import { resolveGeneratedMediaMaxBytes } from "../media/configured-max-bytes.js"; import type { GeneratedVideoAsset, diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index 0e17fb4c5e1e..29c302af5a47 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -428,7 +428,6 @@ export const sharedVitestConfig = { sourcePackageAlias("media-core", "media-source-url"), sourcePackageAlias("media-core", "mime"), sourcePackageAlias("media-core", "read-byte-stream-with-limit"), - sourcePackageAlias("media-core", "read-response-with-limit"), sourcePackageAlias("media-core"), ...sourcePackageAliasesFromExports("acp-core", acpCorePackageJson.exports), ...sourcePluginSdkSubpaths.map((subpath) => ({ diff --git a/tsconfig.json b/tsconfig.json index 5360f34bdd75..3d247db031fa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -106,9 +106,6 @@ "@openclaw/media-core/read-byte-stream-with-limit": [ "./packages/media-core/src/read-byte-stream-with-limit.ts" ], - "@openclaw/media-core/read-response-with-limit": [ - "./packages/media-core/src/read-response-with-limit.ts" - ], "@openclaw/media-core/*": ["./packages/media-core/src/*"], "@openclaw/media-understanding-common": [ "./packages/media-understanding-common/src/index.ts" diff --git a/tsdown.config.ts b/tsdown.config.ts index e23a89e479f2..bbca3d851926 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -455,7 +455,6 @@ function buildMediaCoreDistEntries(): Record { "media-source-url": "packages/media-core/src/media-source-url.ts", mime: "packages/media-core/src/mime.ts", "read-byte-stream-with-limit": "packages/media-core/src/read-byte-stream-with-limit.ts", - "read-response-with-limit": "packages/media-core/src/read-response-with-limit.ts", }; }