mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
refactor(sessions): isolate batch organization adapter
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import type { SessionsPatchManyResult } from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import { readToolStringParam, ToolAuthorizationError, ToolInputError } from "./common.js";
|
||||
import type { AgentToolGatewayRequestCaller } from "./in-process-gateway.js";
|
||||
|
||||
const PATCH_MANY_ERROR_MAX_CHARS = 240;
|
||||
const RESULT_OMITTED_REASON = "response_budget_exceeded";
|
||||
const UNSUPPORTED_PATCH_MANY_FIELDS = [
|
||||
"label",
|
||||
"icon",
|
||||
"statusNote",
|
||||
"attention",
|
||||
"ttlMinutes",
|
||||
"pinned",
|
||||
"archived",
|
||||
"model",
|
||||
"thinkingLevel",
|
||||
] as const;
|
||||
|
||||
type ResolvedPatchTarget = {
|
||||
agentId: string;
|
||||
expectedSessionId?: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
function readPatchCategory(params: Record<string, unknown>): string | null | undefined {
|
||||
const value = params.category;
|
||||
if (value === undefined || value === null) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
throw new ToolInputError("category must be a string");
|
||||
}
|
||||
return value.trim() || null;
|
||||
}
|
||||
|
||||
function readPatchUnread(params: Record<string, unknown>): boolean | undefined {
|
||||
const value = params.unread;
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value !== "boolean") {
|
||||
throw new ToolInputError("unread must be boolean");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function executeSessionsPatchMany(params: {
|
||||
raw: Record<string, unknown>;
|
||||
callGateway: AgentToolGatewayRequestCaller;
|
||||
resolveTarget: (sessionKey: string) => Promise<ResolvedPatchTarget>;
|
||||
resultFitsBudget: (payload: Record<string, unknown>) => boolean;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const targetsInput = params.raw.targets;
|
||||
if (!Array.isArray(targetsInput) || targetsInput.length === 0) {
|
||||
throw new ToolInputError("patch_many requires targets");
|
||||
}
|
||||
if (targetsInput.length > 100) {
|
||||
throw new ToolInputError("patch_many supports at most 100 targets");
|
||||
}
|
||||
for (const field of UNSUPPORTED_PATCH_MANY_FIELDS) {
|
||||
if (params.raw[field] !== undefined) {
|
||||
throw new ToolInputError(`patch_many does not support ${field}`);
|
||||
}
|
||||
}
|
||||
const category = readPatchCategory(params.raw);
|
||||
const unread = readPatchUnread(params.raw);
|
||||
const patch = {
|
||||
...(category !== undefined ? { category } : {}),
|
||||
...(unread !== undefined ? { unread } : {}),
|
||||
};
|
||||
if (Object.keys(patch).length === 0) {
|
||||
throw new ToolInputError("patch_many requires category or unread");
|
||||
}
|
||||
const targets = await Promise.all(
|
||||
targetsInput.map(async (rawTarget, index) => {
|
||||
if (!isRecord(rawTarget)) {
|
||||
throw new ToolInputError(`targets[${index}] must be an object`);
|
||||
}
|
||||
const sessionKey = readToolStringParam(rawTarget, "sessionKey", { required: true });
|
||||
const resolved = await params.resolveTarget(sessionKey);
|
||||
const requestedSessionId = normalizeOptionalString(
|
||||
readToolStringParam(rawTarget, "expectedSessionId"),
|
||||
);
|
||||
if (
|
||||
requestedSessionId &&
|
||||
resolved.expectedSessionId &&
|
||||
requestedSessionId !== resolved.expectedSessionId
|
||||
) {
|
||||
throw new ToolAuthorizationError(`Session changed after access was granted: ${sessionKey}`);
|
||||
}
|
||||
const expectedSessionId = requestedSessionId ?? resolved.expectedSessionId;
|
||||
return {
|
||||
key: resolved.key,
|
||||
...(!parseAgentSessionKey(resolved.key) ? { agentId: resolved.agentId } : {}),
|
||||
...(expectedSessionId ? { expectedSessionId } : {}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
const result = await params.callGateway<SessionsPatchManyResult>({
|
||||
method: "sessions.patchMany",
|
||||
params: { targets, patch },
|
||||
});
|
||||
const failed = result.outcomes.flatMap((outcome) =>
|
||||
outcome.ok
|
||||
? []
|
||||
: [
|
||||
{
|
||||
sessionKey: outcome.key,
|
||||
error: outcome.error.message.slice(0, PATCH_MANY_ERROR_MAX_CHARS),
|
||||
},
|
||||
],
|
||||
);
|
||||
const updated = result.outcomes.length - failed.length;
|
||||
const status = updated === 0 ? "failed" : failed.length > 0 ? "partial" : "updated";
|
||||
const acknowledgement = { status, requested: targets.length, updated, failed };
|
||||
return params.resultFitsBudget(acknowledgement)
|
||||
? acknowledgement
|
||||
: {
|
||||
status,
|
||||
requested: targets.length,
|
||||
updated,
|
||||
failedOmitted: { count: failed.length, reason: RESULT_OMITTED_REASON },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { AgentToolGatewayRequestCaller } from "./in-process-gateway.js";
|
||||
import { createSessionsTool } from "./sessions-tool.js";
|
||||
|
||||
type AgentToolGatewayRequest = Parameters<AgentToolGatewayRequestCaller>[0];
|
||||
|
||||
describe("sessions organization", () => {
|
||||
it("patches and clears category with the other sidebar state", async () => {
|
||||
const callGateway = vi.fn(async () => ({ ok: true }));
|
||||
const tool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
agentSessionId: "session-main",
|
||||
config: {},
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
|
||||
await tool.execute("declare", {
|
||||
action: "patch",
|
||||
label: "Waiting on staging",
|
||||
category: "Blocked",
|
||||
icon: "🦞",
|
||||
statusNote: "Blocked: need the staging password",
|
||||
attention: "key",
|
||||
ttlMinutes: 45,
|
||||
archived: true,
|
||||
});
|
||||
await tool.execute("clear-empty", {
|
||||
action: "patch",
|
||||
label: "",
|
||||
category: "",
|
||||
icon: "",
|
||||
attention: "clear",
|
||||
});
|
||||
await tool.execute("clear-null", { action: "patch", category: null });
|
||||
|
||||
expect(callGateway.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
method: "sessions.patch",
|
||||
params: {
|
||||
key: "agent:main:main",
|
||||
label: "Waiting on staging",
|
||||
category: "Blocked",
|
||||
icon: "🦞",
|
||||
statusNote: "Blocked: need the staging password",
|
||||
attention: "key",
|
||||
ttlMinutes: 45,
|
||||
archived: true,
|
||||
expectedSessionId: "session-main",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
method: "sessions.patch",
|
||||
params: {
|
||||
key: "agent:main:main",
|
||||
label: null,
|
||||
category: null,
|
||||
icon: null,
|
||||
attention: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
method: "sessions.patch",
|
||||
params: { key: "agent:main:main", category: null },
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects empty patches and category targets outside the caller tree", async () => {
|
||||
const callGateway = vi.fn(async () => ({ sessions: [] }));
|
||||
const currentTool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
config: {},
|
||||
callGateway,
|
||||
});
|
||||
await expect(currentTool.execute("patch-empty", { action: "patch" })).rejects.toThrow(
|
||||
"Patch setting required",
|
||||
);
|
||||
|
||||
const restrictedTool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:dashboard:caller",
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
await expect(
|
||||
restrictedTool.execute("patch-other", {
|
||||
action: "patch",
|
||||
sessionKey: "agent:main:other",
|
||||
category: "Projects",
|
||||
}),
|
||||
).rejects.toThrow("Session status visibility is restricted");
|
||||
expect(callGateway).not.toHaveBeenCalledWith({
|
||||
method: "sessions.patch",
|
||||
params: expect.objectContaining({ key: "agent:main:other" }),
|
||||
});
|
||||
});
|
||||
|
||||
it("patches visible sessions in one compact batch result", async () => {
|
||||
const callGateway = vi.fn(async (request: AgentToolGatewayRequest) => {
|
||||
if (request.method === "sessions.patchMany") {
|
||||
return {
|
||||
outcomes: [
|
||||
{ ok: true, key: "agent:main:main" },
|
||||
{
|
||||
ok: false,
|
||||
key: "agent:main:dashboard:changed",
|
||||
error: { code: "INVALID_REQUEST", message: "session changed; retry" },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { key: "agent:main:dashboard:changed" };
|
||||
});
|
||||
const tool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
agentSessionId: "main-session",
|
||||
config: { tools: { sessions: { visibility: "all" } } },
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
|
||||
const result = await tool.execute("batch-category", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main" }, { sessionKey: "agent:main:dashboard:changed" }],
|
||||
category: "Research",
|
||||
unread: false,
|
||||
});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith({
|
||||
method: "sessions.patchMany",
|
||||
params: {
|
||||
targets: [
|
||||
{ key: "agent:main:main", expectedSessionId: "main-session" },
|
||||
{ key: "agent:main:dashboard:changed" },
|
||||
],
|
||||
patch: { category: "Research", unread: false },
|
||||
},
|
||||
});
|
||||
expect(result.details).toEqual({
|
||||
status: "partial",
|
||||
requested: 2,
|
||||
updated: 1,
|
||||
failed: [{ sessionKey: "agent:main:dashboard:changed", error: "session changed; retry" }],
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool.execute("batch-unsupported", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main" }],
|
||||
category: "Research",
|
||||
archived: true,
|
||||
}),
|
||||
).rejects.toThrow("patch_many does not support archived");
|
||||
await expect(
|
||||
tool.execute("batch-stale-current", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main", expectedSessionId: "stale-session" }],
|
||||
unread: false,
|
||||
}),
|
||||
).rejects.toThrow("Session changed after access was granted");
|
||||
});
|
||||
|
||||
it("bounds failed batch details", async () => {
|
||||
const targetKey = "agent:main:dashboard:scoped";
|
||||
const callGateway = vi.fn(async (request: AgentToolGatewayRequest) => {
|
||||
if (request.method === "sessions.patchMany") {
|
||||
return {
|
||||
outcomes: Array.from({ length: 100 }, (_, index) => ({
|
||||
ok: false,
|
||||
key: `${targetKey}:${index}:${"k".repeat(200)}`,
|
||||
error: { code: "INVALID_REQUEST", message: "e".repeat(1_000) },
|
||||
})),
|
||||
};
|
||||
}
|
||||
return { key: targetKey };
|
||||
});
|
||||
const tool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
config: { tools: { sessions: { visibility: "all" } } },
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
|
||||
const result = await tool.execute("batch-bounded", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: targetKey, expectedSessionId: "scoped-session" }],
|
||||
unread: false,
|
||||
});
|
||||
|
||||
expect(result.details).toEqual({
|
||||
status: "failed",
|
||||
requested: 1,
|
||||
updated: 0,
|
||||
failedOmitted: { count: 100, reason: "response_budget_exceeded" },
|
||||
});
|
||||
const text = (result.content[0] as { text?: string } | undefined)?.text ?? "";
|
||||
expect(Buffer.byteLength(text, "utf8")).toBeLessThan(512);
|
||||
});
|
||||
});
|
||||
@@ -20,9 +20,7 @@ import {
|
||||
expectOmittedResolvedAcknowledgement,
|
||||
expectedResolvedOmission,
|
||||
} from "./sessions-tool.test-helpers.js";
|
||||
|
||||
type AgentToolGatewayRequest = Parameters<AgentToolGatewayRequestCaller>[0];
|
||||
|
||||
describe("sessions tool", () => {
|
||||
it("carries the persisted fixed-store owner for a bare patch key", async () => {
|
||||
const callGateway = vi.fn().mockResolvedValue({});
|
||||
@@ -1003,7 +1001,6 @@ describe("sessions tool", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects an empty patch", async () => {
|
||||
const callGateway = vi.fn();
|
||||
const tool = createSessionsTool({
|
||||
@@ -1039,104 +1036,4 @@ describe("sessions tool", () => {
|
||||
params: expect.objectContaining({ key: "agent:main:other" }),
|
||||
});
|
||||
});
|
||||
|
||||
it("patches visible sessions in one compact batch result", async () => {
|
||||
const callGateway = vi.fn(async (request: AgentToolGatewayRequest) => {
|
||||
if (request.method === "sessions.patchMany") {
|
||||
return {
|
||||
outcomes: [
|
||||
{ ok: true, key: "agent:main:main" },
|
||||
{
|
||||
ok: false,
|
||||
key: "agent:main:dashboard:changed",
|
||||
error: { code: "INVALID_REQUEST", message: "session changed; retry" },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { key: "agent:main:dashboard:changed" };
|
||||
});
|
||||
const tool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
agentSessionId: "main-session",
|
||||
config: { tools: { sessions: { visibility: "all" } } },
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
|
||||
const result = await tool.execute("batch-category", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main" }, { sessionKey: "agent:main:dashboard:changed" }],
|
||||
category: "Research",
|
||||
unread: false,
|
||||
});
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith({
|
||||
method: "sessions.patchMany",
|
||||
params: {
|
||||
targets: [
|
||||
{ key: "agent:main:main", expectedSessionId: "main-session" },
|
||||
{ key: "agent:main:dashboard:changed" },
|
||||
],
|
||||
patch: { category: "Research", unread: false },
|
||||
},
|
||||
});
|
||||
expect(result.details).toEqual({
|
||||
status: "partial",
|
||||
requested: 2,
|
||||
updated: 1,
|
||||
failed: [{ sessionKey: "agent:main:dashboard:changed", error: "session changed; retry" }],
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool.execute("batch-unsupported", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main" }],
|
||||
category: "Research",
|
||||
archived: true,
|
||||
}),
|
||||
).rejects.toThrow("patch_many does not support archived");
|
||||
await expect(
|
||||
tool.execute("batch-stale-current", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: "agent:main:main", expectedSessionId: "stale-session" }],
|
||||
unread: false,
|
||||
}),
|
||||
).rejects.toThrow("Session changed after access was granted");
|
||||
});
|
||||
|
||||
it("bounds failed batch details", async () => {
|
||||
const targetKey = "agent:main:dashboard:scoped";
|
||||
const callGateway = vi.fn(async (request: AgentToolGatewayRequest) => {
|
||||
if (request.method === "sessions.patchMany") {
|
||||
return {
|
||||
outcomes: Array.from({ length: 100 }, (_, index) => ({
|
||||
ok: false,
|
||||
key: `${targetKey}:${index}:${"k".repeat(200)}`,
|
||||
error: { code: "INVALID_REQUEST", message: "e".repeat(1_000) },
|
||||
})),
|
||||
};
|
||||
}
|
||||
return { key: targetKey };
|
||||
});
|
||||
const tool = createSessionsTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
config: { tools: { sessions: { visibility: "all" } } },
|
||||
callGateway: callGateway as never,
|
||||
});
|
||||
|
||||
const result = await tool.execute("batch-bounded", {
|
||||
action: "patch_many",
|
||||
targets: [{ sessionKey: targetKey, expectedSessionId: "scoped-session" }],
|
||||
unread: false,
|
||||
});
|
||||
|
||||
expect(result.details).toEqual({
|
||||
status: "failed",
|
||||
requested: 1,
|
||||
updated: 0,
|
||||
failedOmitted: { count: 100, reason: "response_budget_exceeded" },
|
||||
});
|
||||
const text = (result.content[0] as { text?: string } | undefined)?.text ?? "";
|
||||
expect(Buffer.byteLength(text, "utf8")).toBeLessThan(512);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
/** Session self-service tool. */
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { Type } from "typebox";
|
||||
import type {
|
||||
SessionsAssignOwnerResult,
|
||||
SessionsPatchManyResult,
|
||||
SessionsPatchResult,
|
||||
} from "../../../packages/gateway-protocol/src/index.js";
|
||||
import {
|
||||
@@ -46,6 +43,7 @@ import {
|
||||
} from "./sessions-access.js";
|
||||
import { resolveSessionToolContext } from "./sessions-helpers.js";
|
||||
import { resolveSessionReference, shouldResolveSessionIdInput } from "./sessions-resolution.js";
|
||||
import { executeSessionsPatchMany } from "./sessions-tool-patch-many.js";
|
||||
|
||||
const ACTIONS = [
|
||||
"patch",
|
||||
@@ -63,7 +61,6 @@ const GROUP_NAMES_MAX_ITEMS = 200;
|
||||
const SELF_ARCHIVE_MAX_RETRY_DELAY_MS = 5_000;
|
||||
const SESSIONS_TOOL_RESULT_MAX_BYTES = 3_840;
|
||||
const RESOLVED_OMITTED_REASON = "response_budget_exceeded";
|
||||
const PATCH_MANY_ERROR_MAX_CHARS = 240;
|
||||
const SESSION_ICON_GLYPH_DESCRIPTION = SESSION_ICON_GLYPH_IDS.join(", ");
|
||||
const log = createSubsystemLogger("agents/sessions");
|
||||
|
||||
@@ -477,95 +474,18 @@ export function createSessionsTool(opts: SessionsToolOptions = {}): AnyAgentTool
|
||||
);
|
||||
}
|
||||
if (action === "patch_many") {
|
||||
if (!Array.isArray(params.targets) || params.targets.length === 0) {
|
||||
throw new ToolInputError("patch_many requires targets");
|
||||
}
|
||||
if (params.targets.length > 100) {
|
||||
throw new ToolInputError("patch_many supports at most 100 targets");
|
||||
}
|
||||
for (const field of [
|
||||
"label",
|
||||
"icon",
|
||||
"statusNote",
|
||||
"attention",
|
||||
"ttlMinutes",
|
||||
"pinned",
|
||||
"archived",
|
||||
"model",
|
||||
"thinkingLevel",
|
||||
]) {
|
||||
if (params[field] !== undefined) {
|
||||
throw new ToolInputError(`patch_many does not support ${field}`);
|
||||
}
|
||||
}
|
||||
const patch = {
|
||||
...(params.category !== undefined
|
||||
? { category: readClearableString(params, "category") }
|
||||
: {}),
|
||||
...(params.unread !== undefined ? { unread: readBooleanParam(params, "unread") } : {}),
|
||||
};
|
||||
if (Object.keys(patch).length === 0) {
|
||||
throw new ToolInputError("patch_many requires category or unread");
|
||||
}
|
||||
const targets = await Promise.all(
|
||||
params.targets.map(async (rawTarget, index) => {
|
||||
if (!isRecord(rawTarget)) {
|
||||
throw new ToolInputError(`targets[${index}] must be an object`);
|
||||
}
|
||||
const sessionKey = readToolStringParam(rawTarget, "sessionKey", { required: true });
|
||||
const resolved = await resolvePatchTarget(
|
||||
{ ...opts, config: opts.config ?? getRuntimeConfig() },
|
||||
sessionKey,
|
||||
gatewayRequest,
|
||||
);
|
||||
const requestedSessionId = normalizeOptionalString(
|
||||
readToolStringParam(rawTarget, "expectedSessionId"),
|
||||
);
|
||||
if (
|
||||
requestedSessionId &&
|
||||
resolved.expectedSessionId &&
|
||||
requestedSessionId !== resolved.expectedSessionId
|
||||
) {
|
||||
throw new ToolAuthorizationError(
|
||||
`Session changed after access was granted: ${sessionKey}`,
|
||||
);
|
||||
}
|
||||
const expectedSessionId = requestedSessionId ?? resolved.expectedSessionId;
|
||||
return {
|
||||
key: resolved.key,
|
||||
...(!parseAgentSessionKey(resolved.key) ? { agentId: resolved.agentId } : {}),
|
||||
...(expectedSessionId ? { expectedSessionId } : {}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
const result = await callGateway<SessionsPatchManyResult>("sessions.patchMany", {
|
||||
targets,
|
||||
patch,
|
||||
});
|
||||
const failed = result.outcomes.flatMap((outcome) => {
|
||||
if (outcome.ok) {
|
||||
return [];
|
||||
}
|
||||
const error = outcome.error.message.slice(0, PATCH_MANY_ERROR_MAX_CHARS);
|
||||
return [{ sessionKey: outcome.key, error }];
|
||||
});
|
||||
const updated = result.outcomes.length - failed.length;
|
||||
const status = updated === 0 ? "failed" : failed.length > 0 ? "partial" : "updated";
|
||||
const acknowledgement = {
|
||||
status,
|
||||
requested: targets.length,
|
||||
updated,
|
||||
failed,
|
||||
};
|
||||
return jsonResult(
|
||||
sessionsToolResultFitsBudget(acknowledgement)
|
||||
? acknowledgement
|
||||
: {
|
||||
status,
|
||||
requested: targets.length,
|
||||
updated,
|
||||
failedOmitted: { count: failed.length, reason: RESOLVED_OMITTED_REASON },
|
||||
},
|
||||
await executeSessionsPatchMany({
|
||||
raw: params,
|
||||
callGateway: gatewayRequest,
|
||||
resolveTarget: async (sessionKey) =>
|
||||
await resolvePatchTarget(
|
||||
{ ...opts, config: opts.config ?? getRuntimeConfig() },
|
||||
sessionKey,
|
||||
gatewayRequest,
|
||||
),
|
||||
resultFitsBudget: sessionsToolResultFitsBudget,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (action !== "patch") {
|
||||
|
||||
Reference in New Issue
Block a user