Compare commits

..

1 Commits

Author SHA1 Message Date
Vincent Koc
d35b54fe7d fix(plugins): keep openclaw chunks native in jiti 2026-05-30 18:44:35 +02:00
158 changed files with 285 additions and 1236 deletions

View File

@@ -318,11 +318,10 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` keeps one editable status draft for tool progress, clears it at completion, and sends the final answer as a normal message
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- `streaming.progress.commentary` (default `false`) opts into Codex preamble/commentary text in the temporary progress draft. Commentary is cleaned before display, stays transient, and does not change final answer delivery.
- `streaming.preview.commandText` controls command/exec detail inside those tool-progress lines: `raw` (default, preserves released behavior) or `status` (tool label only)
- legacy `channels.telegram.streamMode` and boolean `streaming` values are detected; run `openclaw doctor --fix` to migrate them to `channels.telegram.streaming.mode`
Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, and patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later.
Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, patch summaries, or Codex preamble/commentary text in Codex app-server mode. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later.
Direct chats can use native Telegram drafts for these tool-progress lines without persisting tool chatter into chat history. Native drafts stop before answer text starts; final answers stay on the normal persistent delivery path. This lane is off by default and should be gated to trusted DM IDs first:

View File

@@ -1,14 +1,11 @@
import { EmbeddedBlockChunker } from "openclaw/plugin-sdk/agent-runtime";
import {
buildChannelCommentaryProgressDraftLine,
createChannelProgressDraftGate,
type ChannelProgressDraftLine,
formatChannelProgressDraftText,
isChannelProgressDraftWorkToolName,
mergeChannelProgressDraftLine,
normalizeChannelProgressDraftLineIdentity,
removeChannelProgressDraftLine,
resolveChannelCommentaryProgressLineId,
resolveChannelProgressDraftMaxLines,
resolveChannelStreamingBlockEnabled,
resolveChannelStreamingProgressCommentary,
@@ -126,8 +123,10 @@ export function createDiscordDraftPreviewController(params: {
});
const clearProgressDraftLine = async (lineId: string) => {
const nextLines = removeChannelProgressDraftLine(previewToolProgressLines, lineId);
if (nextLines === previewToolProgressLines) {
const nextLines = previewToolProgressLines.filter(
(line) => typeof line !== "object" || line.id?.trim() !== lineId,
);
if (nextLines.length === previewToolProgressLines.length) {
return;
}
previewToolProgressLines = nextLines;
@@ -308,20 +307,25 @@ export function createDiscordDraftPreviewController(params: {
if (finalReplyStarted || finalReplyDelivered) {
return;
}
const line = buildChannelCommentaryProgressDraftLine({
text,
itemId: options?.itemId,
});
if (!line) {
const lineId = resolveChannelCommentaryProgressLineId({
text,
itemId: options?.itemId,
});
const itemId = options?.itemId?.trim();
if (!text && !itemId) {
return;
}
const normalized = normalizeCommentaryProgressText(text ?? "");
const lineId = itemId ? `commentary:${itemId}` : normalized ? `commentary:${normalized}` : "";
if (!normalized) {
if (lineId) {
await clearProgressDraftLine(lineId);
}
return;
}
const line: ChannelProgressDraftLine = {
id: lineId,
kind: "item",
text: normalized,
label: "Commentary",
prefix: false,
};
previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, {
maxLines: resolveChannelProgressDraftMaxLines(params.discordConfig),
});
@@ -465,6 +469,24 @@ function normalizeReasoningProgressLine(text: string): string {
.trim();
}
function normalizeCommentaryProgressText(text: string): string {
const cleaned = stripInlineDirectiveTagsForDelivery(text).text.trim();
if (!cleaned || isSilentCommentaryProgressText(cleaned)) {
return "";
}
return cleaned
.split(/\r?\n/u)
.map((line) => line.replace(/\s+/g, " ").trim())
.filter(Boolean)
.map((line) => `_${line}_`)
.join("\n");
}
function isSilentCommentaryProgressText(text: string): boolean {
const normalized = text.replace(/^[\s*_`~]+|[\s*_`~]+$/gu, "").trim();
return /^NO_REPLY$/iu.test(normalized);
}
function mergeReasoningProgressText(
current: string,
incoming: string,

View File

@@ -2252,40 +2252,4 @@ describe("shouldIgnoreBoundThreadWebhookMessage", () => {
}),
).toBe(true);
});
it("does not suppress unbound thread webhook echoes when echo expiry overflows", async () => {
const manager = createThreadBindingManager({
cfg: DEFAULT_PREFLIGHT_CFG,
accountId: "default",
persist: false,
enableSweeper: false,
});
const binding = await manager.bindTarget({
threadId: "thread-overflow",
channelId: "parent-1",
targetKind: "subagent",
targetSessionKey: "agent:main:subagent:child-1",
agentId: "main",
webhookId: "wh-overflow",
webhookToken: "tok-1",
});
expect(binding).not.toBeNull();
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
try {
manager.unbindThread({
threadId: "thread-overflow",
sendFarewell: false,
});
} finally {
nowSpy.mockRestore();
}
expect(
shouldIgnoreBoundThreadWebhookMessage({
accountId: "default",
threadId: "thread-overflow",
webhookId: "wh-overflow",
}),
).toBe(false);
});
});

View File

@@ -1,10 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { loadJsonFile, saveJsonFile } from "openclaw/plugin-sdk/json-store";
import {
isFutureDateTimestampMs,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import { normalizeAccountId, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
import {
@@ -349,14 +345,9 @@ export function rememberRecentUnboundWebhookEcho(record: ThreadBindingRecord) {
if (!bindingKey) {
return;
}
const expiresAt = resolveExpiresAtMsFromDurationMs(RECENT_UNBOUND_WEBHOOK_ECHO_WINDOW_MS);
if (expiresAt === undefined) {
RECENT_UNBOUND_WEBHOOK_ECHOES_BY_BINDING_KEY.delete(bindingKey);
return;
}
RECENT_UNBOUND_WEBHOOK_ECHOES_BY_BINDING_KEY.set(bindingKey, {
webhookId,
expiresAt,
expiresAt: Date.now() + RECENT_UNBOUND_WEBHOOK_ECHO_WINDOW_MS,
});
}
@@ -417,7 +408,7 @@ export function isRecentlyUnboundThreadWebhookMessage(params: {
if (!suppressed) {
return false;
}
if (!isFutureDateTimestampMs(suppressed.expiresAt)) {
if (suppressed.expiresAt <= Date.now()) {
RECENT_UNBOUND_WEBHOOK_ECHOES_BY_BINDING_KEY.delete(bindingKey);
return false;
}

View File

@@ -1,7 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { MAX_DATE_TIMESTAMP_MS } from "openclaw/plugin-sdk/number-runtime";
import {
testing as sessionBindingTesting,
registerSessionBindingAdapter,
@@ -322,58 +321,6 @@ describe("matrix monitor handler pairing account scope", () => {
}
});
it("does not reuse account-scoped allowFrom cache while the process clock is invalid", async () => {
const readAllowFromStore = vi.fn(async () => [] as string[]);
const nowSpy = vi.spyOn(Date, "now");
const { handler } = createMatrixHandlerTestHarness({
readAllowFromStore,
dmPolicy: "pairing",
buildPairingReply: () => "pairing",
});
const makeEvent = (id: string): MatrixRawEvent =>
createMatrixTextMessageEvent({
eventId: id,
body: "@room hello",
mentions: { room: true },
});
try {
nowSpy.mockReturnValue(Number.NaN);
await handler("!room:example.org", makeEvent("$event1"));
await handler("!room:example.org", makeEvent("$event2"));
expect(readAllowFromStore).toHaveBeenCalledTimes(2);
} finally {
nowSpy.mockRestore();
}
});
it("does not cache account-scoped allowFrom reads when cache expiry overflows", async () => {
const readAllowFromStore = vi.fn(async () => [] as string[]);
const nowSpy = vi.spyOn(Date, "now");
const { handler } = createMatrixHandlerTestHarness({
readAllowFromStore,
dmPolicy: "pairing",
buildPairingReply: () => "pairing",
});
const makeEvent = (id: string): MatrixRawEvent =>
createMatrixTextMessageEvent({
eventId: id,
body: "@room hello",
mentions: { room: true },
});
try {
nowSpy.mockReturnValue(MAX_DATE_TIMESTAMP_MS);
await handler("!room:example.org", makeEvent("$event1"));
await handler("!room:example.org", makeEvent("$event2"));
expect(readAllowFromStore).toHaveBeenCalledTimes(2);
} finally {
nowSpy.mockRestore();
}
});
it("pins direct-message main route updates to the configured owner", async () => {
const { handler, recordInboundSession } = createMatrixHandlerTestHarness({
cfg: {

View File

@@ -27,10 +27,6 @@ import {
resolveChannelContextVisibilityMode,
} from "openclaw/plugin-sdk/context-visibility-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import {
isFutureDateTimestampMs,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
import { buildInboundHistoryFromEntries } from "openclaw/plugin-sdk/reply-history";
import {
@@ -514,13 +510,9 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
const readStoreAllowFrom = async (): Promise<string[]> => {
const now = Date.now();
if (
cachedStoreAllowFrom &&
isFutureDateTimestampMs(cachedStoreAllowFrom.expiresAtMs, { nowMs: now })
) {
if (cachedStoreAllowFrom && now < cachedStoreAllowFrom.expiresAtMs) {
return cachedStoreAllowFrom.value;
}
cachedStoreAllowFrom = null;
const value = await core.channel.pairing
.readAllowFromStore({
channel: "matrix",
@@ -528,10 +520,10 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
accountId,
})
.catch(() => []);
const expiresAtMs = resolveExpiresAtMsFromDurationMs(ALLOW_FROM_STORE_CACHE_TTL_MS, {
nowMs: now,
});
cachedStoreAllowFrom = expiresAtMs === undefined ? null : { value, expiresAtMs };
cachedStoreAllowFrom = {
value,
expiresAtMs: now + ALLOW_FROM_STORE_CACHE_TTL_MS,
};
return value;
};

View File

@@ -1,34 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { computeFileHash, getCachedFileInfo, setCachedFileInfo } from "./upload-cache.js";
describe("qqbot upload-cache", () => {
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
it("reuses cached file info before expiry", () => {
const hash = computeFileHash("qqbot-cache-hit");
setCachedFileInfo(hash, "group", "target-hit", 1, "file-info-hit", "uuid-hit", 3600);
expect(getCachedFileInfo(hash, "group", "target-hit", 1)).toBe("file-info-hit");
});
it("drops cached file info when the current clock is invalid", () => {
const hash = computeFileHash("qqbot-invalid-clock");
setCachedFileInfo(hash, "group", "target-invalid-clock", 1, "file-info-invalid", "uuid", 3600);
vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
expect(getCachedFileInfo(hash, "group", "target-invalid-clock", 1)).toBeNull();
});
it("does not cache file info when ttl expiry exceeds the Date range", () => {
vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
const hash = computeFileHash("qqbot-overflow");
setCachedFileInfo(hash, "group", "target-overflow", 1, "file-info-overflow", "uuid", 3600);
expect(getCachedFileInfo(hash, "group", "target-overflow", 1)).toBeNull();
});
});

View File

@@ -4,10 +4,6 @@
*/
import * as crypto from "node:crypto";
import {
isFutureDateTimestampMs,
resolveExpiresAtMsFromDurationSeconds,
} from "openclaw/plugin-sdk/number-runtime";
import type { ChatScope } from "../types.js";
import { debugLog } from "./log.js";
@@ -50,7 +46,7 @@ export function getCachedFileInfo(
return null;
}
if (!isFutureDateTimestampMs(entry.expiresAt)) {
if (Date.now() >= entry.expiresAt) {
cache.delete(key);
return null;
}
@@ -72,7 +68,7 @@ export function setCachedFileInfo(
if (cache.size >= MAX_CACHE_SIZE) {
const now = Date.now();
for (const [k, v] of cache) {
if (!isFutureDateTimestampMs(v.expiresAt, { nowMs: now })) {
if (now >= v.expiresAt) {
cache.delete(k);
}
}
@@ -87,16 +83,11 @@ export function setCachedFileInfo(
const key = buildCacheKey(contentHash, scope, targetId, fileType);
const safetyMargin = 60;
const effectiveTtl = Math.max(ttl - safetyMargin, 10);
const expiresAt = resolveExpiresAtMsFromDurationSeconds(effectiveTtl);
if (expiresAt === undefined) {
cache.delete(key);
return;
}
cache.set(key, {
fileInfo,
fileUuid,
expiresAt,
expiresAt: Date.now() + effectiveTtl * 1000,
});
debugLog(

View File

@@ -2076,118 +2076,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect(deliverReplies).not.toHaveBeenCalled();
});
it("hides Telegram commentary progress unless explicitly enabled", async () => {
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onItemEvent?.({
itemId: "preamble-1",
kind: "preamble",
progressText: "Checking private context before replying.",
});
return { queuedFinal: false };
});
await dispatchWithContext({
context: createContext(),
streamMode: "progress",
telegramCfg: { streaming: { mode: "progress", progress: { label: false } } },
});
expect(answerDraftStream.update).not.toHaveBeenCalled();
});
it("shows opt-in Telegram commentary progress through the shared progress draft", async () => {
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onItemEvent?.({
itemId: "preamble-1",
kind: "preamble",
progressText: "Checking the current weather source before summarizing.",
});
await replyOptions?.onItemEvent?.({
itemId: "preamble-1",
kind: "preamble",
progressText: "Checking the current weather source before summarizing clearly.",
});
await replyOptions?.onItemEvent?.({
itemId: "preamble-2",
kind: "preamble",
progressText: "[[reply_to_current]] Checking route impacts.",
});
await replyOptions?.onItemEvent?.({
itemId: "preamble-2",
kind: "preamble",
progressText: "NO_REPLY",
});
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
return { queuedFinal: false };
});
await dispatchWithContext({
context: createContext(),
streamMode: "progress",
telegramCfg: {
streaming: {
mode: "progress",
progress: {
label: false,
toolProgress: false,
commentary: true,
},
},
},
});
expect(answerDraftStream.update).toHaveBeenLastCalledWith(
"_Checking the current weather source before summarizing clearly._",
);
const updates = answerDraftStream.update.mock.calls.map((call) => call[0]).join("\n");
expect(updates).not.toContain("Exec");
expect(updates).not.toContain("reply_to_current");
expect(updates).not.toContain("NO_REPLY");
});
it("keeps Telegram progress drafts usable after the last commentary line becomes silent", async () => {
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onItemEvent?.({
itemId: "preamble-1",
kind: "preamble",
progressText: "Temporary note.",
});
await replyOptions?.onItemEvent?.({
itemId: "preamble-1",
kind: "preamble",
progressText: "NO_REPLY",
});
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
return { queuedFinal: false };
});
await dispatchWithContext({
context: createContext(),
streamMode: "progress",
telegramCfg: {
streaming: {
mode: "progress",
progress: {
label: false,
commentary: true,
},
},
},
});
expect(answerDraftStream.clear).toHaveBeenCalled();
expect(answerDraftStream.forceNewMessage).toHaveBeenCalled();
const clearOrder = answerDraftStream.clear.mock.invocationCallOrder[0];
const forceNewMessageOrder = answerDraftStream.forceNewMessage.mock.invocationCallOrder[0];
const lastUpdateOrder = answerDraftStream.update.mock.invocationCallOrder.at(-1)!;
expect(clearOrder).toBeLessThan(forceNewMessageOrder);
expect(forceNewMessageOrder).toBeLessThan(lastUpdateOrder);
expect(answerDraftStream.update).toHaveBeenLastCalledWith("`🛠️ Exec`");
});
it("does not restart progress drafts after final answer delivery", async () => {
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

View File

@@ -23,7 +23,6 @@ import {
projectOutboundPayloadPlanForDelivery,
} from "openclaw/plugin-sdk/channel-outbound";
import {
buildChannelCommentaryProgressDraftLine,
buildChannelProgressDraftLineForEntry,
createChannelProgressDraftGate,
type ChannelProgressDraftLine,
@@ -32,11 +31,8 @@ import {
formatChannelProgressDraftText,
isChannelProgressDraftWorkToolName,
mergeChannelProgressDraftLine,
removeChannelProgressDraftLine,
resolveChannelCommentaryProgressLineId,
resolveChannelProgressDraftMaxLines,
resolveChannelStreamingBlockEnabled,
resolveChannelStreamingProgressCommentary,
resolveChannelStreamingPreviewNativeToolProgress,
resolveChannelStreamingPreviewNativeToolProgressAllowFrom,
resolveChannelStreamingPreviewToolProgress,
@@ -924,8 +920,6 @@ export const dispatchTelegramMessage = async ({
const reasoningLane = lanes.reasoning;
const streamToolProgressEnabled =
Boolean(answerLane.stream) && resolveChannelStreamingPreviewToolProgress(telegramCfg);
const commentaryProgressEnabled =
Boolean(answerLane.stream) && resolveChannelStreamingProgressCommentary(telegramCfg);
const nativeToolProgressDraft =
streamToolProgressEnabled &&
!isRoomEvent &&
@@ -988,26 +982,6 @@ export const dispatchTelegramMessage = async ({
});
let finalAnswerDeliveryStarted = false;
let finalAnswerDelivered = false;
const clearStreamProgressDraftLine = async (lineId: string) => {
const nextLines = removeChannelProgressDraftLine(streamToolProgressLines, lineId);
if (nextLines === streamToolProgressLines) {
return false;
}
streamToolProgressLines = nextLines;
if (!progressDraftGate.hasStarted) {
return true;
}
if (await renderProgressDraft()) {
return true;
}
answerLane.lastPartialText = "";
answerLane.hasStreamedMessage = false;
answerLane.finalized = false;
resetAnswerToolProgressDraft();
await answerLane.stream?.clear();
answerLane.stream?.forceNewMessage();
return true;
};
const pushStreamToolProgress = async (
line?: string | ChannelProgressDraftLine,
options?: { toolName?: string; startImmediately?: boolean },
@@ -1088,34 +1062,6 @@ export const dispatchTelegramMessage = async ({
}
return false;
};
const pushCommentaryProgress = async (text?: string, options?: { itemId?: string }) => {
if (!answerLane.stream || streamMode !== "progress" || !commentaryProgressEnabled) {
return false;
}
if (answerLane.finalized || finalAnswerDeliveryStarted || finalAnswerDelivered) {
return false;
}
const line = buildChannelCommentaryProgressDraftLine({
text,
itemId: options?.itemId,
});
if (!line) {
const lineId = resolveChannelCommentaryProgressLineId({
text,
itemId: options?.itemId,
});
return lineId ? await clearStreamProgressDraftLine(lineId) : false;
}
const nextLines = mergeChannelProgressDraftLine(streamToolProgressLines, line, {
maxLines: resolveChannelProgressDraftMaxLines(telegramCfg),
});
if (nextLines === streamToolProgressLines) {
return false;
}
streamToolProgressLines = nextLines;
await progressDraftGate.startNow();
return await renderProgressDraft();
};
let splitReasoningOnNextStream = false;
let draftLaneEventQueue = Promise.resolve();
const reasoningStepState = createTelegramReasoningStepState();
@@ -2057,12 +2003,6 @@ export const dispatchTelegramMessage = async ({
await progressPromise;
},
onItemEvent: async (payload) => {
if (payload.kind === "preamble") {
await pushCommentaryProgress(payload.progressText, {
itemId: payload.itemId,
});
return;
}
await pushStreamToolProgress(
buildChannelProgressDraftLineForEntry(telegramCfg, {
event: "item",

View File

@@ -89,10 +89,6 @@ export const telegramChannelConfigUiHints = {
label: "Telegram Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commentary": {
label: "Telegram Progress Commentary",
help: "Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged.",
},
"streaming.progress.commandText": {
label: "Telegram Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',

View File

@@ -336,37 +336,6 @@ describe("RealtimeCallHandler path routing", () => {
}
});
it("rejects stream sessions when token expiry would exceed the Date range", async () => {
const processEvent = vi.fn();
const createBridge = vi.fn(() => makeBridge());
const handler = makeHandler(undefined, {
manager: {
processEvent,
},
provider: {
name: "telnyx",
},
realtimeProvider: makeRealtimeProvider(createBridge),
});
handler.setPublicUrl("https://public.example/voice/webhook");
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
const session = handler.issueStreamSession({
providerName: "telnyx",
callId: "call-overflow",
direction: "inbound",
});
nowSpy.mockRestore();
const server = await startStreamSessionServer(handler, session.streamUrl);
try {
await expect(connectWs(server.url)).rejects.toThrow("Unexpected server response: 401");
expect(createBridge).not.toHaveBeenCalled();
expect(processEvent).not.toHaveBeenCalled();
} finally {
await server.close();
}
});
it("rejects Telnyx stream starts that do not match the token-bound call", async () => {
const processEvent = vi.fn();
const getCall = vi.fn(

View File

@@ -3,10 +3,6 @@ import http from "node:http";
import type { Duplex } from "node:stream";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import {
isFutureDateTimestampMs,
resolveExpiresAtMsFromDurationMs,
} from "openclaw/plugin-sdk/number-runtime";
import {
buildRealtimeVoiceAgentConsultWorkingResponse,
createRealtimeVoiceForcedConsultCoordinator,
@@ -506,13 +502,9 @@ export class RealtimeCallHandler {
private issueStreamToken(meta: Omit<PendingStreamToken, "expiry"> = {}): string {
const token = randomUUID();
const now = Date.now();
const expiry = resolveExpiresAtMsFromDurationMs(STREAM_TOKEN_TTL_MS, { nowMs: now });
if (expiry !== undefined) {
this.pendingStreamTokens.set(token, { expiry, ...meta });
}
this.pendingStreamTokens.set(token, { expiry: Date.now() + STREAM_TOKEN_TTL_MS, ...meta });
for (const [candidate, entry] of this.pendingStreamTokens) {
if (!isFutureDateTimestampMs(entry.expiry, { nowMs: now })) {
if (Date.now() > entry.expiry) {
this.pendingStreamTokens.delete(candidate);
}
}
@@ -525,7 +517,7 @@ export class RealtimeCallHandler {
return null;
}
this.pendingStreamTokens.delete(token);
if (!isFutureDateTimestampMs(entry.expiry)) {
if (Date.now() > entry.expiry) {
return null;
}
return {

View File

@@ -5,10 +5,8 @@ import path from "node:path";
import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
import {
asFiniteNumberInRange,
isFutureDateTimestampMs,
parseStrictFiniteNumber,
parseStrictNonNegativeInteger,
resolveExpiresAtMsFromDurationMs,
resolveTimerTimeoutMs,
} from "openclaw/plugin-sdk/number-runtime";
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
@@ -849,7 +847,7 @@ function readCachedGroupContext(profile: string, groupId: string): ZaloGroupCont
if (!cached) {
return null;
}
if (!isFutureDateTimestampMs(cached.expiresAt)) {
if (cached.expiresAt <= Date.now()) {
groupContextCache.delete(key);
return null;
}
@@ -861,7 +859,7 @@ function readCachedGroupContext(profile: string, groupId: string): ZaloGroupCont
function trimGroupContextCache(now: number): void {
for (const [key, value] of groupContextCache) {
if (isFutureDateTimestampMs(value.expiresAt, { nowMs: now })) {
if (value.expiresAt > now) {
continue;
}
groupContextCache.delete(key);
@@ -881,13 +879,9 @@ function writeCachedGroupContext(profile: string, context: ZaloGroupContext): vo
if (groupContextCache.has(key)) {
groupContextCache.delete(key);
}
const expiresAt = resolveExpiresAtMsFromDurationMs(GROUP_CONTEXT_CACHE_TTL_MS, { nowMs: now });
if (expiresAt === undefined) {
return;
}
groupContextCache.set(key, {
value: context,
expiresAt,
expiresAt: now + GROUP_CONTEXT_CACHE_TTL_MS,
});
trimGroupContextCache(now);
}
@@ -985,9 +979,6 @@ function toInboundMessage(message: Message, ownUserId?: string): ZaloInboundMess
export const testing = {
toInboundMessage,
readCachedGroupContext,
writeCachedGroupContext,
clearCachedGroupContext,
};
export { testing as __testing };

View File

@@ -75,30 +75,3 @@ describe("Zalo inbound timestamp normalization", () => {
expect(inboundTimestamp("9007199254740993")).toBe(1_700_000_000_000);
});
});
describe("Zalo group context cache", () => {
afterEach(() => {
zaloTesting.clearCachedGroupContext("cache-profile");
});
it("drops cached group context when the current clock is invalid", () => {
zaloTesting.writeCachedGroupContext("cache-profile", {
groupId: "group-invalid-clock",
name: "Cached",
});
vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
expect(zaloTesting.readCachedGroupContext("cache-profile", "group-invalid-clock")).toBeNull();
});
it("does not cache group context when ttl expiry exceeds the Date range", () => {
vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
zaloTesting.writeCachedGroupContext("cache-profile", {
groupId: "group-overflow",
name: "Overflow",
});
expect(zaloTesting.readCachedGroupContext("cache-profile", "group-overflow")).toBeNull();
});
});

View File

@@ -1,7 +1,7 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { coerceSecretRef } from "../config/types.secrets.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles.js";
import { normalizeProviderId } from "./provider-id.js";
type AgentApiKeyCredential = { type: "api_key"; key: string };
type AgentOAuthCredential = {

View File

@@ -1,5 +1,4 @@
import path from "node:path";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { Model } from "../llm/types.js";
import { normalizeModelCompat } from "../plugins/provider-model-compat.js";
@@ -15,6 +14,7 @@ import {
} from "./agent-auth-discovery.js";
import { resolveModelPluginMetadataSnapshot } from "./model-discovery-context.js";
import type { PluginModelCatalogMetadataSnapshot } from "./plugin-model-catalog.js";
import { normalizeProviderId } from "./provider-id.js";
import {
AuthStorage,
ModelRegistry,

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { AgentCompactionMode } from "../config/types.agent-defaults.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { ContextEngineInfo } from "../context-engine/types.js";
import { MIN_PROMPT_BUDGET_RATIO, MIN_PROMPT_BUDGET_TOKENS } from "./agent-compaction-constants.js";
import { resolveProviderEndpoint } from "./provider-attribution.js";
import { normalizeProviderId } from "./provider-id.js";
export const DEFAULT_AGENT_COMPACTION_RESERVE_TOKENS_FLOOR = 20_000;

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { getLoadedChannelPlugin } from "../channels/plugins/index.js";
import { resolveSessionConversation } from "../channels/plugins/session-conversation.js";
import { DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH } from "../config/agent-limits.js";
@@ -22,6 +21,7 @@ import {
import { normalizeMessageChannel } from "../utils/message-channel.js";
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
import type { AnyAgentTool } from "./agent-tools.types.js";
import { normalizeProviderId } from "./provider-id.js";
import { pickSandboxToolPolicy } from "./sandbox-tool-policy.js";
import type { SandboxToolPolicy } from "./sandbox.js";
import {

View File

@@ -1,7 +1,3 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeUniqueStringEntries } from "../shared/string-normalization.js";
import {
@@ -15,6 +11,7 @@ import { resolveEffectiveOAuthCredential } from "./auth-profiles/effective-oauth
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles/types.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
type AuthProfileSource = "store";

View File

@@ -1,6 +1,6 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { buildProviderAuthDoctorHintWithPlugin } from "../../plugins/provider-runtime.runtime.js";
import { normalizeProviderId } from "../provider-id.js";
import type { AuthProfileStore } from "./types.js";
const QWEN_PORTAL_OAUTH_MIGRATION_HINT =

View File

@@ -1,10 +1,7 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveCliRuntimeExecutionProvider } from "../model-runtime-aliases.js";
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import { findNormalizedProviderValue, normalizeProviderId } from "../provider-id.js";
import { CLAUDE_CLI_PROFILE_ID } from "./constants.js";
import type { AuthProfileStore } from "./types.js";

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
resolveAgentModelFallbackValues,
resolveAgentModelPrimaryValue,
} from "../../config/model-input.js";
import type { AgentModelConfig } from "../../config/types.agents-shared.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { normalizeProviderId } from "../provider-id.js";
export type ExternalCliAuthScope = {
providerIds: string[];

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
readClaudeCliCredentialsCached,
readCodexCliCredentialsCached,
readMiniMaxCliCredentialsCached,
} from "../cli-credentials.js";
import { normalizeProviderId } from "../provider-id.js";
import {
CLAUDE_CLI_PROFILE_ID,
EXTERNAL_CLI_SYNC_TTL_MS,

View File

@@ -1,6 +1,6 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { sanitizeForLog } from "../../../packages/terminal-core/src/ansi.js";
import { formatCliCommand } from "../../cli/command-format.js";
import { normalizeProviderId } from "../provider-id.js";
export type OAuthRefreshFailureReason =
| "refresh_token_reused"

View File

@@ -1,9 +1,6 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import { findNormalizedProviderValue, normalizeProviderId } from "../provider-id.js";
import {
evaluateStoredCredentialEligibility,
type AuthCredentialReasonCode,

View File

@@ -1,11 +1,11 @@
import { createHash } from "node:crypto";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveOAuthPath } from "../../config/paths.js";
import { coerceSecretRef } from "../../config/types.secrets.js";
import { loadJsonFile } from "../../infra/json-file.js";
import { isRecord } from "../../shared/record-coerce.js";
import { uniqueStrings } from "../../shared/string-normalization.js";
import { asBoolean } from "../../utils/boolean.js";
import { normalizeProviderId } from "../provider-id.js";
import { AUTH_STORE_VERSION, log } from "./constants.js";
import {
isLegacyOAuthRef,

View File

@@ -1,10 +1,7 @@
import {
findNormalizedProviderKey,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import { normalizeStringEntries } from "../../shared/string-normalization.js";
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.js";
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
import {
ensureAuthProfileStoreForLocalUpdate,

View File

@@ -1,9 +1,6 @@
import {
findNormalizedProviderKey,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { AuthProfileConfig } from "../../config/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.js";
import { resolveAuthProfileMetadata } from "./identity.js";
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
import type { AuthProfileIdRepairResult, AuthProfileStore } from "./types.js";

View File

@@ -1,11 +1,11 @@
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { loadJsonFile, repairJsonFilePermissions, saveJsonFile } from "../../infra/json-file.js";
import { asFiniteNumber } from "../../shared/number-coercion.js";
import { isRecord } from "../../shared/record-coerce.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { normalizeTrimmedStringList } from "../../shared/string-normalization.js";
import { normalizeProviderId } from "../provider-id.js";
import { AUTH_STORE_VERSION } from "./constants.js";
import { resolveAuthStatePath } from "./paths.js";
import type {

View File

@@ -1,4 +1,4 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../provider-id.js";
import type { AuthProfileStore, ProfileUsageStats } from "./types.js";
export function isAuthCooldownBypassedForProvider(provider: string | undefined): boolean {

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import {
positiveSecondsToSafeMilliseconds,
resolveExpiresAtMsFromEpochSeconds,
} from "../../shared/number-coercion.js";
import { normalizeProviderId } from "../provider-id.js";
import { resolveProviderRequestHeaders } from "../provider-request-config.js";
import { logAuthProfileFailureStateChange } from "./state-observation.js";

View File

@@ -1,12 +1,10 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MAX_DATE_TIMESTAMP_MS } from "../shared/number-coercion.js";
import {
consumeExecApprovalFollowupRuntimeHandoff,
resetExecApprovalFollowupRuntimeHandoffsForTests,
} from "./bash-tools.exec-approval-followup-state.js";
import {
buildExecApprovalPendingToolResult,
createExecApprovalPendingState,
enforceStrictInlineEvalApprovalBoundary,
MAX_EXEC_APPROVAL_FOLLOWUP_FAILURE_LOG_KEYS as maxExecApprovalFollowupFailureLogKeys,
resolveExecApprovalUnavailableState,
@@ -41,60 +39,6 @@ vi.mock("../infra/exec-approvals.js", async (importOriginal) => {
};
});
describe("createExecApprovalPendingState", () => {
it("sets a valid pending approval expiry from the current clock", () => {
const nowSpy = vi.spyOn(Date, "now");
try {
nowSpy.mockReturnValue(1_800_000_000_000);
expect(
createExecApprovalPendingState({
warnings: ["careful"],
timeoutMs: 60_000,
}),
).toMatchObject({
warningText: "careful\n\n",
expiresAtMs: 1_800_000_060_000,
preResolvedDecision: undefined,
});
} finally {
nowSpy.mockRestore();
}
});
it("expires pending approvals immediately while the process clock is invalid", () => {
const nowSpy = vi.spyOn(Date, "now");
try {
nowSpy.mockReturnValue(Number.NaN);
expect(
createExecApprovalPendingState({
warnings: [],
timeoutMs: 60_000,
}).expiresAtMs,
).toBe(0);
} finally {
nowSpy.mockRestore();
}
});
it("expires pending approvals immediately when expiry would exceed Date bounds", () => {
const nowSpy = vi.spyOn(Date, "now");
try {
nowSpy.mockReturnValue(MAX_DATE_TIMESTAMP_MS);
expect(
createExecApprovalPendingState({
warnings: [],
timeoutMs: 60_000,
}).expiresAtMs,
).toBe(0);
} finally {
nowSpy.mockRestore();
}
});
});
describe("sendExecApprovalFollowupResult", () => {
const sendExecApprovalFollowup = vi.fn();
const logWarn = vi.fn();

View File

@@ -15,7 +15,6 @@ import {
type ExecSecurity,
} from "../infra/exec-approvals.js";
import { logWarn } from "../logger.js";
import { resolveExpiresAtMsFromDurationMs } from "../shared/number-coercion.js";
import { registerExecApprovalFollowupRuntimeHandoff } from "./bash-tools.exec-approval-followup-state.js";
import { sendExecApprovalFollowup } from "./bash-tools.exec-approval-followup.js";
import {
@@ -64,8 +63,6 @@ export type ExecApprovalRequestState = ExecApprovalPendingState & {
noticeSeconds: number;
};
const EXPIRED_EXEC_APPROVAL_EXPIRES_AT_MS = 0;
export type ExecApprovalUnavailableReason =
| "no-approval-route"
| "initiating-platform-disabled"
@@ -114,11 +111,9 @@ export function createExecApprovalPendingState(params: {
warnings: string[];
timeoutMs: number;
}): ExecApprovalPendingState {
const expiresAtMs =
resolveExpiresAtMsFromDurationMs(params.timeoutMs) ?? EXPIRED_EXEC_APPROVAL_EXPIRES_AT_MS;
return {
warningText: params.warnings.length ? `${params.warnings.join("\n")}\n\n` : "",
expiresAtMs,
expiresAtMs: Date.now() + params.timeoutMs,
preResolvedDecision: undefined,
};
}

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { CliBackendConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { ContextEngineHostCapability } from "../context-engine/types.js";
@@ -19,6 +18,7 @@ import type {
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { uniqueStrings } from "../shared/string-normalization.js";
import { mergePluginTextTransforms } from "./plugin-text-transforms.js";
import { normalizeProviderId } from "./provider-id.js";
type CliBackendsDeps = {
resolvePluginSetupCliBackend: typeof resolvePluginSetupCliBackend;

View File

@@ -1,6 +1,6 @@
import { findNormalizedProviderValue } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveProviderEndpoint } from "./provider-attribution.js";
import { findNormalizedProviderValue } from "./provider-id.js";
export const CONTEXT_WINDOW_HARD_MIN_TOKENS = 4_000;
export const CONTEXT_WINDOW_WARN_BELOW_TOKENS = 8_000;

View File

@@ -1,6 +1,6 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import { normalizeProviderId } from "../provider-id.js";
import type { AgentMessage } from "../runtime/index.js";
const THREAD_SUFFIX_REGEX = /^(.*)(?::(?:thread|topic):\d+)$/i;

View File

@@ -1,14 +1,14 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { planManifestModelCatalogRows } from "../../model-catalog/manifest-planner.js";
import type { NormalizedModelCatalogRow } from "../../model-catalog/types.js";
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
import { listOpenClawPluginManifestMetadata } from "../../plugins/manifest-metadata-scan.js";
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
import { loadPluginManifest } from "../../plugins/manifest.js";
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
import { normalizeStaticProviderModelId } from "../model-ref-shared.js";
import { normalizeProviderId } from "../provider-id.js";
function rowMatchesModel(params: {
row: NormalizedModelCatalogRow;
@@ -28,8 +28,8 @@ function rowMatchesModel(params: {
function normalizeStaticCatalogInput(
input: NormalizedModelCatalogRow["input"],
): ProviderRuntimeModel["input"] {
const normalizedInput = input.filter(
(item): item is "text" | "image" => item === "text" || item === "image",
const normalizedInput = input.filter((item): item is "text" | "image" =>
item === "text" || item === "image"
);
return normalizedInput.length > 0 ? normalizedInput : ["text"];
}

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isRecord } from "../utils.js";
import { OPENCLAW_AGENT_RUNTIME_ID, isDefaultAgentRuntimeId } from "./agent-runtime-id.js";
import { normalizeOptionalAgentRuntimeId } from "./agent-runtime-id.js";
import { resolveAgentHarnessPolicy } from "./harness/policy.js";
import { normalizeProviderId } from "./provider-id.js";
function normalizeConfiguredRuntimeId(value: unknown): string | undefined {
return normalizeOptionalAgentRuntimeId(value);

View File

@@ -1,7 +1,3 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { Model } from "../llm/types.js";
import type {
@@ -12,6 +8,7 @@ import type { ProviderResolveDynamicModelContext } from "../plugins/types.js";
import { createLazyImportLoader } from "../shared/lazy-promise.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { listPrioritizedHighSignalLiveModelRefs } from "./live-model-filter.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
type ProviderRuntimeModule = typeof import("../plugins/provider-runtime.js");
type DynamicModelResolver = typeof runProviderDynamicModel;

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveProviderModernModelRef } from "../plugins/provider-runtime.js";
import { parseStrictNonNegativeInteger } from "../shared/number-coercion.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { liveProvidersShareOwningPlugin } from "./live-provider-owner.js";
import { normalizeProviderId } from "./provider-id.js";
type ModelRef = {
provider?: string | null;

View File

@@ -14,8 +14,8 @@ import {
} from "./model-selection.js";
export { LiveSessionModelSwitchError } from "./live-model-switch-error.js";
export type LiveSessionModelSelection = EmbeddedRunModelSwitchRequest;
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeProviderId } from "./provider-id.js";
const OPENAI_PROVIDER_ID = "openai";
const OPENAI_CODEX_PROVIDER_ID = "openai-codex";

View File

@@ -1,6 +1,6 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveOwningPluginIdsForProviderRef } from "../plugins/providers.js";
import { normalizeProviderId } from "./provider-id.js";
type LiveProviderOwnerContext = {
config?: OpenClawConfig;

View File

@@ -1,11 +1,11 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeGooglePreviewModelId } from "@openclaw/model-catalog-core/provider-model-id-normalize";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeGooglePreviewModelId } from "../plugin-sdk/provider-model-id-normalize.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
import { liveProvidersShareOwningPlugin } from "./live-provider-owner.js";
import { normalizeProviderId } from "./provider-id.js";
type ModelTarget = {
raw: string;

View File

@@ -1,9 +1,5 @@
import os from "node:os";
import path from "node:path";
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig, MemorySearchConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import type { SecretInput } from "../config/types.secrets.js";
@@ -17,6 +13,7 @@ import { getMemoryEmbeddingProvider } from "../plugins/memory-embedding-provider
import { normalizeStringEntries, uniqueStrings } from "../shared/string-normalization.js";
import { clampInt, clampNumber, resolveUserPath } from "../utils.js";
import { resolveAgentConfig } from "./agent-scope.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
export type ResolvedMemorySearchConfig = {
enabled: boolean;

View File

@@ -1,6 +1,5 @@
import fs from "node:fs";
import os from "node:os";
import { normalizeProviderIdForAuth } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
import { resolvePluginSetupProvider } from "../plugins/setup-registry.js";
@@ -9,6 +8,7 @@ import { normalizeOptionalString as normalizeOptionalPathInput } from "../shared
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
import { resolveProviderEnvAuthLookupMaps } from "./model-auth-env-vars.js";
import { GCP_VERTEX_CREDENTIALS_MARKER } from "./model-auth-markers.js";
import { normalizeProviderIdForAuth } from "./provider-id.js";
export type EnvApiKeyResult = {
apiKey: string;

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import type { ModelCatalogEntry, ModelInputType } from "./model-catalog.types.js";
import { normalizeProviderId } from "./provider-id.js";
export function modelSupportsInput(
entry: ModelCatalogEntry | undefined,

View File

@@ -1,9 +1,6 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeUniqueSingleOrTrimmedStringList } from "../shared/string-normalization.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
function dedupeCatalogScopeRefs(values: Array<string | undefined>): string[] {
return normalizeUniqueSingleOrTrimmedStringList(values);

View File

@@ -1,6 +1,5 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { getRuntimeConfig } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
@@ -38,6 +37,7 @@ import {
listPluginModelCatalogFiles,
type PluginModelCatalogMetadataSnapshot,
} from "./plugin-model-catalog.js";
import { normalizeProviderId } from "./provider-id.js";
const log = createSubsystemLogger("model-catalog");
const AGENT_CUSTOM_MODEL_DEFAULT_CONTEXT_WINDOW = 128_000;

View File

@@ -1,7 +1,7 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { listCliRuntimeProviderIds } from "./cli-backends.js";
import { isCliRuntimeProvider } from "./model-runtime-aliases.js";
import { normalizeProviderId } from "./provider-id.js";
const RETIRED_MODEL_PICKER_PROVIDERS = new Set(["codex", "codex-cli"]);

View File

@@ -1,13 +1,14 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderModelIdWithManifest } from "../plugins/manifest-model-id-normalization.js";
import {
collectManifestModelIdNormalizationPolicies,
type ManifestModelIdNormalizationRecord,
normalizeBuiltInProviderModelId,
normalizeConfiguredProviderCatalogModelRef,
normalizeConfiguredProviderCatalogModelId as normalizeConfiguredProviderCatalogModelIdShared,
normalizeStaticProviderModelIdWithPolicies,
} from "@openclaw/model-catalog-core/provider-model-id-normalization";
import { normalizeProviderModelIdWithManifest } from "../plugins/manifest-model-id-normalization.js";
} from "../shared/provider-model-id-normalization.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { normalizeProviderId } from "./provider-id.js";
type StaticModelRef = {
provider: string;
@@ -19,22 +20,6 @@ export type ProviderModelIdNormalizationOptions = {
manifestPlugins?: readonly ManifestModelIdNormalizationRecord[];
};
export type ManifestModelIdNormalizationProvider = {
aliases?: Record<string, string>;
stripPrefixes?: string[];
prefixWhenBare?: string;
prefixWhenBareAfterAliasStartsWith?: {
modelPrefix: string;
prefix: string;
}[];
};
export type ManifestModelIdNormalizationRecord = {
modelIdNormalization?: {
providers?: Record<string, ManifestModelIdNormalizationProvider>;
};
};
export function modelKey(provider: string, model: string): string {
const providerId = provider.trim();
const modelId = model.trim();

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import {
@@ -9,6 +8,7 @@ import {
} from "./cli-backends.js";
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
import { normalizeProviderId } from "./provider-id.js";
const RUNTIME_COMPARISON_PROVIDER_ALIASES = new Map<string, string>([["openai-codex", "openai"]]);

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js";
import type { AgentRuntimePolicyConfig } from "../config/types.agents-shared.js";
import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeAgentId } from "../routing/session-key.js";
import { listAgentEntries, resolveSessionAgentIds } from "./agent-scope.js";
import { normalizeProviderId } from "./provider-id.js";
export type ModelRuntimePolicySource = "model" | "provider";

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { Type } from "typebox";
import { formatErrorMessage } from "../infra/errors.js";
import { getEnvApiKey } from "../llm/env-api-keys.js";
@@ -12,6 +11,7 @@ import {
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { normalizeStringEntries, uniqueStrings } from "../shared/string-normalization.js";
import { normalizeProviderId } from "./provider-id.js";
const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
const DEFAULT_TIMEOUT_MS = 12_000;

View File

@@ -1,12 +1,12 @@
import {
findNormalizedProviderKey as findNormalizedProviderKeyCore,
findNormalizedProviderValue as findNormalizedProviderValueCore,
normalizeProviderId as normalizeProviderIdCore,
normalizeProviderIdForAuth as normalizeProviderIdForAuthCore,
} from "@openclaw/model-catalog-core/provider-id";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { modelKey as sharedModelKey, normalizeStaticProviderModelId } from "./model-ref-shared.js";
import {
findNormalizedProviderKey,
findNormalizedProviderValue,
normalizeProviderId,
normalizeProviderIdForAuth,
} from "./provider-id.js";
import { normalizeProviderModelIdWithRuntime } from "./provider-model-normalization.runtime.js";
export type ModelRef = {
@@ -33,27 +33,12 @@ export function legacyModelKey(provider: string, model: string): string | null {
return rawKey === canonicalKey ? null : rawKey;
}
export function normalizeProviderId(provider: string): string {
return normalizeProviderIdCore(provider);
}
export function normalizeProviderIdForAuth(provider: string): string {
return normalizeProviderIdForAuthCore(provider);
}
export function findNormalizedProviderValue<T>(
entries: Record<string, T> | undefined,
provider: string,
): T | undefined {
return findNormalizedProviderValueCore(entries, provider);
}
export function findNormalizedProviderKey(
entries: Record<string, unknown> | undefined,
provider: string,
): string | undefined {
return findNormalizedProviderKeyCore(entries, provider);
}
export {
findNormalizedProviderKey,
findNormalizedProviderValue,
normalizeProviderId,
normalizeProviderIdForAuth,
};
function normalizeProviderModelId(
provider: string,

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
buildManifestBuiltInModelSuppressionResolver,
resolveManifestBuiltInModelSuppression,
} from "../plugins/manifest-model-suppression.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { normalizeProviderId } from "./provider-id.js";
function resolveBuiltInModelSuppressionFromManifest(params: {
provider?: string | null;

View File

@@ -1,7 +1,3 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { formatErrorMessage } from "../infra/errors.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
@@ -31,6 +27,7 @@ import {
createProviderApiKeyResolver,
createProviderAuthResolver,
} from "./models-config.providers.secrets.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
const log = createSubsystemLogger("agents/model-providers");

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveSecretInputRef } from "../config/types.secrets.js";
import { resolveProviderSyntheticAuthWithPlugin } from "../plugins/provider-runtime.js";
@@ -21,6 +20,7 @@ import {
type ProviderAuthResolver,
} from "./models-config.providers.secret-helpers.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
import { normalizeProviderId } from "./provider-id.js";
export type {
ProfileApiKeyResolution,

View File

@@ -1,5 +1,4 @@
import { writeSync } from "node:fs";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { type Api, completeSimple, type Model } from "openclaw/plugin-sdk/llm";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
@@ -63,6 +62,7 @@ import {
import { getApiKeyForModel, requireApiKey } from "./model-auth.js";
import { shouldSuppressBuiltInModel } from "./model-suppression.js";
import { ensureOpenClawModelsJson } from "./models-config.js";
import { normalizeProviderId } from "./provider-id.js";
import { prepareModelForSimpleCompletion } from "./simple-completion-transport.js";
const LIVE = isLiveTestEnabled();

View File

@@ -1,5 +1,5 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeProviderId } from "./provider-id.js";
export const OPENAI_PROVIDER_ID = "openai";
export const OPENAI_CODEX_PROVIDER_ID = "openai-codex";

View File

@@ -1,7 +1,7 @@
import { existsSync, readdirSync } from "node:fs";
import path from "node:path";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import { normalizeProviderId } from "./provider-id.js";
export const PLUGIN_MODEL_CATALOG_FILE = "catalog.json";
export const PLUGIN_MODEL_CATALOG_GENERATED_BY = "openclaw-plugin-model-catalog-v1";

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { listOpenClawPluginManifestMetadata } from "../plugins/manifest-metadata-scan.js";
import { isRecord } from "../shared/record-coerce.js";
import {
@@ -10,6 +9,7 @@ import { normalizeTrimmedStringList } from "../shared/string-normalization.js";
import { asBoolean } from "../utils/boolean.js";
import type { RuntimeVersionEnv } from "../version.js";
import { resolveRuntimeServiceVersion } from "../version.js";
import { normalizeProviderId } from "./provider-id.js";
type ProviderAttributionVerification =
| "vendor-documented"

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizePluginsConfig } from "../plugins/config-state.js";
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
@@ -11,6 +10,7 @@ import { resolvePluginControlPlaneFingerprint } from "../plugins/plugin-control-
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import type { PluginOrigin } from "../plugins/plugin-origin.types.js";
import { normalizeProviderId } from "./provider-id.js";
export type ProviderAuthAliasLookupParams = {
config?: OpenClawConfig;

View File

@@ -0,0 +1,6 @@
export {
findNormalizedProviderKey,
findNormalizedProviderValue,
normalizeProviderId,
normalizeProviderIdForAuth,
} from "../../packages/model-catalog-core/src/provider-id.js";

View File

@@ -1,7 +1,3 @@
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import type { OpenClawConfig } from "../config/config.js";
import { extractModelCompat } from "../plugins/provider-model-compat.js";
import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
@@ -16,6 +12,7 @@ import { resolveEffectiveToolPolicy } from "./agent-tools.policy.js";
import { resolveModel } from "./embedded-agent-runner/model.js";
import { resolveBundledStaticCatalogModel } from "./embedded-agent-runner/model.static-catalog.js";
import { normalizeStaticProviderModelId } from "./model-ref-shared.js";
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
import { normalizeToolName } from "./tool-policy.js";
import {
buildEffectiveToolInventoryGroups,

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
findCapabilityProviderById,
resolveCapabilityModelRefForProviders,
@@ -20,6 +19,7 @@ import {
import { uniqueStrings } from "../../shared/string-normalization.js";
import type { AuthProfileStore } from "../auth-profiles/types.js";
import { normalizeModelRef } from "../model-selection.js";
import { normalizeProviderId } from "../provider-id.js";
import {
ToolInputError,
readPositiveIntegerParam,

View File

@@ -1,4 +1,3 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
import { resolveContextTokensForModel } from "../../agents/context.js";
import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js";
@@ -7,6 +6,7 @@ import {
OPENAI_PROVIDER_ID,
resolveContextConfigProviderForRuntime,
} from "../../agents/openai-codex-routing.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { logVerbose } from "../../globals.js";
import { createLazyImportLoader } from "../../shared/lazy-promise.js";

View File

@@ -1,5 +1,4 @@
import crypto from "node:crypto";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
clearAutoFallbackPrimaryProbeSelection,
hasSessionAutoModelFallbackProvenance,
@@ -13,6 +12,7 @@ import { resolveFastModeState } from "../../agents/fast-mode.js";
import { runAgentHarnessBeforeMessageWriteHook } from "../../agents/harness/hook-helpers.js";
import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js";
import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import { resolveIngressWorkspaceOverrideForSpawnedRun } from "../../agents/spawned-context.js";
import type { SilentReplyPromptMode } from "../../agents/system-prompt.types.js";
import { normalizeChatType } from "../../channels/chat-type.js";

View File

@@ -1,6 +1,6 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { splitTrailingAuthProfile } from "../../agents/model-ref-profile.js";
import { isModelKeyAllowedBySet } from "../../agents/model-selection-shared.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
export type ModelAliasIndex = {

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { ModelCatalogEntry } from "../../agents/model-catalog.types.js";
import {
buildAllowedModelSetWithFallbacks,
isModelKeyAllowedBySet,
} from "../../agents/model-selection-shared.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import { resolveAgentModelFallbackValues } from "../../config/model-input.js";
import type { SessionEntry } from "../../config/sessions.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";

View File

@@ -1,4 +1,4 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../agents/provider-id.js";
import {
BASE_THINKING_LEVELS,
normalizeThinkLevel,

View File

@@ -12,7 +12,6 @@ import type {
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { normalizeTrimmedStringList } from "../shared/string-normalization.js";
import { asBoolean } from "../utils/boolean.js";
import { stripInlineDirectiveTagsForDelivery } from "../utils/directive-tags.js";
export type {
ChannelDeliveryStreamingConfig,
@@ -276,7 +275,6 @@ export type ChannelProgressDraftLine = {
status?: string;
toolName?: string;
prefix?: boolean;
format?: boolean;
};
function compactStrings(values: readonly (string | undefined | null)[]): string[] {
@@ -515,70 +513,6 @@ export function buildChannelProgressDraftLine(
return undefined;
}
export function normalizeChannelCommentaryProgressText(text: string): string {
const cleaned = stripInlineDirectiveTagsForDelivery(text).text.trim();
if (!cleaned || isSilentChannelCommentaryProgressText(cleaned)) {
return "";
}
return cleaned
.split(/\r?\n/u)
.map((line) => line.replace(/\s+/g, " ").trim())
.filter(Boolean)
.map((line) => `_${line}_`)
.join("\n");
}
export function resolveChannelCommentaryProgressLineId(params: {
text?: string;
itemId?: string;
}): string | undefined {
const itemId = params.itemId?.trim();
if (itemId) {
return `commentary:${itemId}`;
}
const normalized = normalizeChannelCommentaryProgressText(params.text ?? "");
return normalized ? `commentary:${normalized}` : undefined;
}
export function buildChannelCommentaryProgressDraftLine(params: {
text?: string;
itemId?: string;
}): ChannelProgressDraftLine | undefined {
const normalized = normalizeChannelCommentaryProgressText(params.text ?? "");
const itemId = params.itemId?.trim();
const lineId = itemId ? `commentary:${itemId}` : normalized ? `commentary:${normalized}` : "";
if (!normalized || !lineId) {
return undefined;
}
return {
id: lineId,
kind: "item",
text: normalized,
label: "Commentary",
prefix: false,
format: false,
};
}
export function removeChannelProgressDraftLine<TLine extends string | ChannelProgressDraftLine>(
lines: TLine[],
lineId: string,
): TLine[] {
const normalizedLineId = lineId.trim();
if (!normalizedLineId) {
return lines;
}
const nextLines = lines.filter(
(line) => typeof line !== "object" || line.id?.trim() !== normalizedLineId,
);
return nextLines.length === lines.length ? lines : nextLines;
}
function isSilentChannelCommentaryProgressText(text: string): boolean {
const normalized = text.replace(/^[\s*_`~]+|[\s*_`~]+$/gu, "").trim();
return /^NO_REPLY$/iu.test(normalized);
}
export function createChannelProgressDraftGate(params: {
onStart: () => void | Promise<void>;
initialDelayMs?: number;
@@ -1055,17 +989,14 @@ export function formatChannelProgressDraftText(params: {
? line
: getProgressDraftLineText(line);
const text = compactChannelProgressDraftLine(rawText, maxLineChars);
const format =
!isLabelLine && typeof line === "object" && line !== null ? line.format !== false : true;
return text ? { text, isLabelLine, prefix, format } : undefined;
return text ? { text, isLabelLine, prefix } : undefined;
})
.filter(
(line): line is { text: string; isLabelLine: boolean; prefix: boolean; format: boolean } =>
Boolean(line),
.filter((line): line is { text: string; isLabelLine: boolean; prefix: boolean } =>
Boolean(line),
)
.slice(-maxLines)
.map(({ text, isLabelLine, prefix, format }) => {
const formatted = isLabelLine || !format ? text : formatLine(text);
.map(({ text, isLabelLine, prefix }) => {
const formatted = isLabelLine ? text : formatLine(text);
return {
text:
!isLabelLine && prefix && shouldPrefixProgressLine(text)

View File

@@ -1,5 +1,5 @@
import { collectConfiguredModelRefs } from "@openclaw/model-catalog-core/configured-model-refs";
import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
import { collectConfiguredModelRefs } from "../config/model-refs.js";
import type { AuthProfileConfig } from "../config/types.auth.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {

View File

@@ -1,8 +1,4 @@
import fsSync from "node:fs";
import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import { note } from "../../packages/terminal-core/src/note.js";
import {
resolveAgentDir,
@@ -16,6 +12,7 @@ import {
resolveEnvApiKey,
resolveUsableCustomProviderApiKey,
} from "../agents/model-auth.js";
import { findNormalizedProviderValue, normalizeProviderId } from "../agents/provider-id.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { formatErrorMessage } from "../infra/errors.js";

View File

@@ -1,6 +1,4 @@
import fs from "node:fs";
import { AGENT_MODEL_CONFIG_KEYS } from "@openclaw/model-catalog-core/configured-model-refs";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeOptionalAgentRuntimeId } from "../../../agents/agent-runtime-id.js";
import { resolveConfiguredProviderFallback } from "../../../agents/configured-provider-fallback.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js";
@@ -8,6 +6,8 @@ import { splitTrailingAuthProfile } from "../../../agents/model-ref-profile.js";
import { normalizeConfiguredProviderCatalogModelId } from "../../../agents/model-ref-shared.js";
import { resolveModelRuntimePolicy } from "../../../agents/model-runtime-policy.js";
import { openAIProviderUsesCodexRuntimeByDefault } from "../../../agents/openai-codex-routing.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import { AGENT_MODEL_CONFIG_KEYS } from "../../../config/model-refs.js";
import { loadSessionStore, updateSessionStore } from "../../../config/sessions/store.js";
import { resolveAllAgentSessionStoreTargetsSync } from "../../../config/sessions/targets.js";
import type { SessionEntry } from "../../../config/sessions/types.js";

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeEmbeddedAgentRuntime } from "../../../agents/agent-runtime-id.js";
import { resolveDefaultAgentDir } from "../../../agents/agent-scope-config.js";
import { resolveCliBackendConfig } from "../../../agents/cli-backends.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js";
import { resolveAgentHarnessPolicy } from "../../../agents/harness/policy.js";
import { getRegisteredAgentHarness } from "../../../agents/harness/registry.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import {
buildGenericCliContextEngineHostSupport,

View File

@@ -1,5 +1,5 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { sanitizeForLog } from "../../../../packages/terminal-core/src/ansi.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-promotion-helpers.js";
import { resolveNormalizedProviderModelMaxTokens } from "../../../config/defaults.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";

View File

@@ -1,4 +1,4 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import { isKnownCoreToolId } from "../../../agents/tool-catalog.js";
import { isToolAllowedByPolicyName } from "../../../agents/tool-policy-match.js";
import { resolveToolProfilePolicy } from "../../../agents/tool-policy-shared.js";

View File

@@ -1,5 +1,5 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { splitTrailingAuthProfile } from "../../../agents/model-ref-profile.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import {
defineLegacyConfigMigration,
ensureRecord,

View File

@@ -1,4 +1,4 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import type { ModelDefinitionConfig } from "../../../config/types.models.js";
const LEGACY_MODELS_ADD_CODEX_MODEL_IDS = new Set(["gpt-5.5", "gpt-5.5-pro"]);

View File

@@ -1,5 +1,5 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeStaticProviderModelId } from "../../../agents/model-ref-shared.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
type LegacyRuntimeModelProviderAlias = {
/** Legacy provider id that encoded the runtime in the model ref. */

View File

@@ -1,5 +1,5 @@
import fs from "node:fs";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import {
extractShippedPluginInstallConfigRecords,
stripShippedPluginInstallConfigRecords,

View File

@@ -1,8 +1,8 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { sanitizeServerName, TOOL_NAME_SEPARATOR } from "../../../agents/agent-bundle-mcp-names.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js";
import { compileGlobPatterns, matchesAnyGlobPattern } from "../../../agents/glob-pattern.js";
import { parseModelRef } from "../../../agents/model-selection-normalize.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import {
mergeAlsoAllowPolicy,
normalizeToolName,

View File

@@ -1,7 +1,7 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveAgentConfig } from "../../../agents/agent-scope-config.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js";
import { parseModelRef } from "../../../agents/model-selection-normalize.js";
import { normalizeProviderId } from "../../../agents/provider-id.js";
import { pickSandboxToolPolicy } from "../../../agents/sandbox-tool-policy.js";
import {
isToolAllowedByPolicies,

View File

@@ -1,8 +1,8 @@
import { collectConfiguredModelRefs } from "@openclaw/model-catalog-core/configured-model-refs";
import { collectConfiguredAgentHarnessRuntimes } from "../../../agents/harness-runtimes.js";
import { listPotentialConfiguredChannelPresenceSignals } from "../../../channels/config-presence.js";
import { normalizeChatChannelId } from "../../../channels/registry.js";
import { isChannelConfigured } from "../../../config/channel-configured.js";
import { collectConfiguredModelRefs } from "../../../config/model-refs.js";
import { detectPluginAutoEnableCandidates } from "../../../config/plugin-auto-enable.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { compareOpenClawVersions } from "../../../config/version.js";

View File

@@ -1,4 +1,3 @@
import { normalizeProviderIdForAuth } from "@openclaw/model-catalog-core/provider-id";
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
import {
listProviderEnvAuthLookupKeys,
@@ -15,6 +14,7 @@ import {
OPENAI_PROVIDER_ID,
openAIProviderUsesCodexRuntimeByDefault,
} from "../../agents/openai-codex-routing.js";
import { normalizeProviderIdForAuth } from "../../agents/provider-id.js";
import { resolveAgentModelPrimaryValue } from "../../config/model-input.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.types.js";

View File

@@ -1,4 +1,3 @@
import { normalizeProviderIdForAuth } from "@openclaw/model-catalog-core/provider-id";
import { formatRemainingShort } from "../../agents/auth-health.js";
import { resolveAuthProfileDisplayLabel } from "../../agents/auth-profiles/display.js";
import { resolveAuthStorePathForDisplay } from "../../agents/auth-profiles/paths.js";
@@ -12,6 +11,7 @@ import {
resolveEnvApiKey,
resolveUsableCustomProviderApiKey,
} from "../../agents/model-auth.js";
import { normalizeProviderIdForAuth } from "../../agents/provider-id.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { ProviderAuthEvidence } from "../../secrets/provider-env-vars.js";
import {

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { loadAuthProfileStoreWithoutExternalProfiles } from "../../agents/auth-profiles/store.js";
import {
createProviderApiKeyResolver,
createProviderAuthResolver,
} from "../../agents/models-config.providers.secrets.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import type { ModelProviderConfig } from "../../config/types.models.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { formatErrorMessage } from "../../infra/errors.js";

View File

@@ -1,9 +1,9 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import {
shouldSuppressBuiltInModel,
shouldSuppressBuiltInModelFromManifest,
} from "../../agents/model-suppression.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import type { ModelDefinitionConfig, ModelProviderConfig } from "../../config/types.models.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { ModelRegistry } from "../../llm/model-registry.js";

View File

@@ -1,8 +1,8 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveModelAgentRuntimeMetadata } from "../agents/agent-runtime-metadata.js";
import { resolveConfiguredProviderFallback } from "../agents/configured-provider-fallback.js";
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { parseModelRef, resolvePersistedSelectedModelRef } from "../agents/model-selection.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.js";

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,10 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
import {
collectManifestModelIdNormalizationPolicies,
normalizeConfiguredProviderCatalogModelId,
} from "@openclaw/model-catalog-core/provider-model-id-normalization";
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
} from "../shared/provider-model-id-normalization.js";
import { isRecord } from "../shared/record-coerce.js";
import {
DEFAULT_AGENT_MAX_CONCURRENT,

View File

@@ -2,7 +2,6 @@ import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { collectManifestModelIdNormalizationPolicies } from "@openclaw/model-catalog-core/provider-model-id-normalization";
import JSON5 from "json5";
import { sanitizeTerminalText } from "../../packages/terminal-core/src/safe-text.js";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope-config.js";
@@ -28,6 +27,7 @@ import {
resolvePluginMetadataSnapshot,
type PluginMetadataSnapshot,
} from "../plugins/plugin-metadata-snapshot.js";
import { collectManifestModelIdNormalizationPolicies } from "../shared/provider-model-id-normalization.js";
import { isRecord } from "../utils.js";
import { VERSION } from "../version.js";
import { DuplicateAgentDirError, findDuplicateAgentDirs } from "./agent-dirs.js";

View File

@@ -1,6 +1,9 @@
import { isDeepStrictEqual } from "node:util";
import { normalizeConfiguredProviderCatalogModelId } from "@openclaw/model-catalog-core/provider-model-id-normalization";
import { parseConfigPathArrayIndex } from "../shared/path-array-index.js";
import {
type ManifestModelIdNormalizationProvider,
normalizeConfiguredProviderCatalogModelId,
} from "../shared/provider-model-id-normalization.js";
import { isRecord } from "../utils.js";
import { applyMergePatch } from "./merge-patch.js";
import { normalizeAgentModelMapForConfig, normalizeAgentModelRefForConfig } from "./model-input.js";
@@ -12,16 +15,6 @@ const OPEN_DM_POLICY_ALLOW_FROM_RE =
const MANAGED_CONFIG_UNSET_PATHS = [["plugins", "installs"]] as const;
type ManifestModelIdNormalizationProvider = {
aliases?: Record<string, string>;
stripPrefixes?: string[];
prefixWhenBare?: string;
prefixWhenBareAfterAliasStartsWith?: {
modelPrefix: string;
prefix: string;
}[];
};
function cloneUnknown<T>(value: T): T {
return structuredClone(value);
}

View File

@@ -1,8 +1,8 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { normalizeProviderId } from "../agents/provider-id.js";
import {
normalizeGooglePreviewModelId,
normalizeTogetherModelId,
} from "@openclaw/model-catalog-core/provider-model-id-normalize";
} from "../plugin-sdk/provider-model-id-normalize.js";
import { isRecord as isPlainRecord } from "../shared/record-coerce.js";
import {
normalizeLowercaseStringOrEmpty,

7
src/config/model-refs.ts Normal file
View File

@@ -0,0 +1,7 @@
export {
AGENT_MODEL_CONFIG_KEYS,
collectConfiguredModelRefs,
collectConfiguredModelRefValues,
extractProviderFromModelRef,
type ConfiguredModelRef,
} from "../../packages/model-catalog-core/src/configured-model-refs.js";

View File

@@ -1,6 +1,5 @@
import { collectConfiguredModelRefs } from "@openclaw/model-catalog-core/configured-model-refs";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import {
listPotentialConfiguredChannelPresenceSignals,
type ChannelPresenceSignalSource,
@@ -24,6 +23,7 @@ import { resolvePluginSetupAutoEnableReasons } from "../plugins/setup-registry.j
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { isRecord } from "../utils.js";
import { isChannelConfigured } from "./channel-configured.js";
import { collectConfiguredModelRefs } from "./model-refs.js";
import { shouldSkipPreferredPluginAutoEnable } from "./plugin-auto-enable.prefer-over.js";
import type {
PluginAutoEnableCandidate,

View File

@@ -238,7 +238,7 @@ describe("config schema", () => {
expect(progressPropsFor("discord")).not.toHaveProperty("nativeTaskCards");
expect(progressPropsFor("telegram")).not.toHaveProperty("nativeTaskCards");
expect(progressPropsFor("discord")).toHaveProperty("commentary");
expect(progressPropsFor("telegram")).toHaveProperty("commentary");
expect(progressPropsFor("telegram")).not.toHaveProperty("commentary");
expect(res.uiHints["channels.matrix"]?.label).toBe("Matrix");
expect(res.uiHints["channels.matrix.accessToken"]?.sensitive).toBe(true);
expect(res.uiHints["channels.matrix.streaming.progress.label"]?.label).toBe(
@@ -252,9 +252,7 @@ describe("config schema", () => {
expect(res.uiHints["channels.discord.streaming.progress.toolProgress"]?.label).toBe(
"Discord Progress Tool Lines",
);
expect(res.uiHints["channels.telegram.streaming.progress.commentary"]?.label).toBe(
"Telegram Progress Commentary",
);
expect(res.uiHints["channels.telegram.streaming.progress.commentary"]).toBeUndefined();
expect(res.uiHints["channels.mattermost.streaming.progress.label"]?.label).toBe(
"Mattermost Progress Label",
);
@@ -412,7 +410,7 @@ describe("config schema", () => {
).toBe(false);
});
it("accepts progress commentary for channels that render commentary drafts", () => {
it("accepts progress commentary only for Discord streaming config", () => {
expect(
DiscordConfigSchema.safeParse({
streaming: {
@@ -429,7 +427,7 @@ describe("config schema", () => {
progress: { commentary: true },
},
}).success,
).toBe(true);
).toBe(false);
});
it("keeps per-agent model overrides limited to model selection", () => {

View File

@@ -120,13 +120,6 @@ function readValidPromptBlob(storePath: string, ref: SessionSkillPromptRef): str
}
}
export function isSessionSkillPromptBlobReadable(
storePath: string,
ref: SessionSkillPromptRef,
): boolean {
return readValidPromptBlob(storePath, ref) !== null;
}
async function ensurePromptBlob(storePath: string, prompt: string): Promise<SessionSkillPromptRef> {
const ref = buildPromptRef(prompt);
const blobPath = resolveSessionSkillPromptBlobPath(storePath, ref.hash);

View File

@@ -19,7 +19,6 @@ import { deriveSessionMetaPatch } from "./metadata.js";
import { resolveStorePath } from "./paths.js";
import {
ensureSessionStorePromptBlobsForPersistence,
isSessionSkillPromptBlobReadable,
projectSessionStoreForPersistence,
type SessionSkillPromptBlobProjection,
} from "./skill-prompt-blobs.js";
@@ -59,7 +58,6 @@ import {
mergeSessionEntry,
mergeSessionEntryPreserveActivity,
type SessionEntry,
type SessionSkillPromptRef,
} from "./types.js";
export {
@@ -91,6 +89,12 @@ const writerStoreFileStats = new WeakMap<
Record<string, SessionEntry>,
ReturnType<typeof getFileStatSnapshot> | null
>();
let serializedPromptRefKeyCache:
| {
serialized: string;
keys: Set<string>;
}
| undefined;
function loadSessionArchiveRuntime() {
sessionArchiveRuntimePromise ??= import("../../gateway/session-archive.runtime.js");
@@ -367,20 +371,23 @@ function buildSingleEntrySerializedStore(params: {
};
}
function collectSerializedPromptRefs(serialized: string): Map<string, SessionSkillPromptRef> {
const refs = new Map<string, SessionSkillPromptRef>();
function collectSerializedPromptRefKeys(serialized: string): Set<string> {
if (serializedPromptRefKeyCache?.serialized === serialized) {
return serializedPromptRefKeyCache.keys;
}
const keys = new Set<string>();
try {
const parsed = JSON.parse(serialized) as Record<string, SessionEntry>;
for (const [key, entry] of Object.entries(parsed)) {
const ref = entry?.skillsSnapshot?.promptRef;
if (ref) {
refs.set(key, ref);
if (entry?.skillsSnapshot?.promptRef) {
keys.add(key);
}
}
} catch {
// Malformed serialized cache cannot prove prompt refs are already durable.
}
return refs;
serializedPromptRefKeyCache = { serialized, keys };
return keys;
}
function storeHasUnsafeUntouchedHydratedSkillPrompts(
@@ -389,15 +396,15 @@ function storeHasUnsafeUntouchedHydratedSkillPrompts(
changedSessionKey: string,
): boolean {
const currentSerialized = getSerializedSessionStore(storePath);
const serializedPromptRefs = currentSerialized
? collectSerializedPromptRefs(currentSerialized)
const serializedPromptRefKeys = currentSerialized
? collectSerializedPromptRefKeys(currentSerialized)
: undefined;
for (const [key, entry] of Object.entries(store)) {
if (key === changedSessionKey || typeof entry.skillsSnapshot?.prompt !== "string") {
continue;
}
const ref = serializedPromptRefs?.get(key);
if (!ref || !isSessionSkillPromptBlobReadable(storePath, ref)) {
if (
key !== changedSessionKey &&
typeof entry.skillsSnapshot?.prompt === "string" &&
!serializedPromptRefKeys?.has(key)
) {
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More