fix(tools): harden tool schemas for strict providers
This commit is contained in:
@@ -100,6 +100,7 @@
|
|||||||
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
||||||
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
||||||
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
||||||
|
- Tool schema guardrails (google-antigravity): avoid `Type.Union` in tool input schemas; no `anyOf`/`oneOf`/`allOf`. Use `stringEnum`/`optionalStringEnum` (Type.Unsafe enum) for string lists, and `Type.Optional(...)` instead of `... | null`. Keep top-level tool schema as `type: "object"` with `properties`.
|
||||||
- When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/agents/main/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
|
- When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/agents/main/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
|
||||||
- Menubar dimming + restart flow mirrors Trimmy: use `scripts/restart-mac.sh` (kills all Clawdbot variants, runs `swift build`, packages, relaunches). Icon dimming depends on MenuBarExtraAccess wiring in AppMain; keep `appearsDisabled` updates intact when touching the status item.
|
- Menubar dimming + restart flow mirrors Trimmy: use `scripts/restart-mac.sh` (kills all Clawdbot variants, runs `swift build`, packages, relaunches). Icon dimming depends on MenuBarExtraAccess wiring in AppMain; keep `appearsDisabled` updates intact when touching the status item.
|
||||||
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
|
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
|
||||||
|
|||||||
@@ -40,20 +40,6 @@ const DEFAULT_PATH =
|
|||||||
process.env.PATH ??
|
process.env.PATH ??
|
||||||
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
||||||
|
|
||||||
// NOTE: Using Type.Unsafe with enum instead of Type.Union([Type.Literal(...)])
|
|
||||||
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
|
||||||
// Type.Union of literals compiles to { anyOf: [{enum:["a"]}, {enum:["b"]}, ...] }
|
|
||||||
// which is valid but not accepted. A flat enum { type: "string", enum: [...] } works.
|
|
||||||
const _stringEnum = <T extends readonly string[]>(
|
|
||||||
values: T,
|
|
||||||
options?: { description?: string },
|
|
||||||
) =>
|
|
||||||
Type.Unsafe<T[number]>({
|
|
||||||
type: "string",
|
|
||||||
enum: values as unknown as string[],
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ExecToolDefaults = {
|
export type ExecToolDefaults = {
|
||||||
backgroundMs?: number;
|
backgroundMs?: number;
|
||||||
timeoutSec?: number;
|
timeoutSec?: number;
|
||||||
|
|||||||
27
src/agents/schema/typebox.ts
Normal file
27
src/agents/schema/typebox.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
type StringEnumOptions<T extends readonly string[]> = {
|
||||||
|
description?: string;
|
||||||
|
title?: string;
|
||||||
|
default?: T[number];
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: Avoid Type.Union([Type.Literal(...)]) which compiles to anyOf.
|
||||||
|
// Some providers reject anyOf in tool schemas; a flat string enum is safer.
|
||||||
|
export function stringEnum<T extends readonly string[]>(
|
||||||
|
values: T,
|
||||||
|
options: StringEnumOptions<T> = {},
|
||||||
|
) {
|
||||||
|
return Type.Unsafe<T[number]>({
|
||||||
|
type: "string",
|
||||||
|
enum: [...values],
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function optionalStringEnum<T extends readonly string[]>(
|
||||||
|
values: T,
|
||||||
|
options: StringEnumOptions<T> = {},
|
||||||
|
) {
|
||||||
|
return Type.Optional(stringEnum(values, options));
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { resolveBrowserConfig } from "../../browser/config.js";
|
import { resolveBrowserConfig } from "../../browser/config.js";
|
||||||
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../browser/constants.js";
|
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../browser/constants.js";
|
||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||||
import {
|
import {
|
||||||
type AnyAgentTool,
|
type AnyAgentTool,
|
||||||
imageResultFromFile,
|
imageResultFromFile,
|
||||||
@@ -43,16 +44,35 @@ const BROWSER_ACT_KINDS = [
|
|||||||
"close",
|
"close",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type BrowserActKind = (typeof BROWSER_ACT_KINDS)[number];
|
const BROWSER_TOOL_ACTIONS = [
|
||||||
|
"status",
|
||||||
|
"start",
|
||||||
|
"stop",
|
||||||
|
"tabs",
|
||||||
|
"open",
|
||||||
|
"focus",
|
||||||
|
"close",
|
||||||
|
"snapshot",
|
||||||
|
"screenshot",
|
||||||
|
"navigate",
|
||||||
|
"console",
|
||||||
|
"pdf",
|
||||||
|
"upload",
|
||||||
|
"dialog",
|
||||||
|
"act",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const BROWSER_TARGETS = ["sandbox", "host", "custom"] as const;
|
||||||
|
|
||||||
|
const BROWSER_SNAPSHOT_FORMATS = ["aria", "ai"] as const;
|
||||||
|
|
||||||
|
const BROWSER_IMAGE_TYPES = ["png", "jpeg"] as const;
|
||||||
|
|
||||||
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
||||||
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
||||||
// The discriminator (kind) determines which properties are relevant; runtime validates.
|
// The discriminator (kind) determines which properties are relevant; runtime validates.
|
||||||
const BrowserActSchema = Type.Object({
|
const BrowserActSchema = Type.Object({
|
||||||
kind: Type.Unsafe<BrowserActKind>({
|
kind: stringEnum(BROWSER_ACT_KINDS),
|
||||||
type: "string",
|
|
||||||
enum: [...BROWSER_ACT_KINDS],
|
|
||||||
}),
|
|
||||||
// Common fields
|
// Common fields
|
||||||
targetId: Type.Optional(Type.String()),
|
targetId: Type.Optional(Type.String()),
|
||||||
ref: Type.Optional(Type.String()),
|
ref: Type.Optional(Type.String()),
|
||||||
@@ -89,37 +109,15 @@ const BrowserActSchema = Type.Object({
|
|||||||
// A root-level `Type.Union([...])` compiles to `{ anyOf: [...] }` (no `type`),
|
// A root-level `Type.Union([...])` compiles to `{ anyOf: [...] }` (no `type`),
|
||||||
// which OpenAI rejects ("Invalid schema ... type: None"). Keep this schema an object.
|
// which OpenAI rejects ("Invalid schema ... type: None"). Keep this schema an object.
|
||||||
const BrowserToolSchema = Type.Object({
|
const BrowserToolSchema = Type.Object({
|
||||||
action: Type.Union([
|
action: stringEnum(BROWSER_TOOL_ACTIONS),
|
||||||
Type.Literal("status"),
|
target: optionalStringEnum(BROWSER_TARGETS),
|
||||||
Type.Literal("start"),
|
|
||||||
Type.Literal("stop"),
|
|
||||||
Type.Literal("tabs"),
|
|
||||||
Type.Literal("open"),
|
|
||||||
Type.Literal("focus"),
|
|
||||||
Type.Literal("close"),
|
|
||||||
Type.Literal("snapshot"),
|
|
||||||
Type.Literal("screenshot"),
|
|
||||||
Type.Literal("navigate"),
|
|
||||||
Type.Literal("console"),
|
|
||||||
Type.Literal("pdf"),
|
|
||||||
Type.Literal("upload"),
|
|
||||||
Type.Literal("dialog"),
|
|
||||||
Type.Literal("act"),
|
|
||||||
]),
|
|
||||||
target: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("sandbox"),
|
|
||||||
Type.Literal("host"),
|
|
||||||
Type.Literal("custom"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
profile: Type.Optional(Type.String()),
|
profile: Type.Optional(Type.String()),
|
||||||
controlUrl: Type.Optional(Type.String()),
|
controlUrl: Type.Optional(Type.String()),
|
||||||
targetUrl: Type.Optional(Type.String()),
|
targetUrl: Type.Optional(Type.String()),
|
||||||
targetId: Type.Optional(Type.String()),
|
targetId: Type.Optional(Type.String()),
|
||||||
limit: Type.Optional(Type.Number()),
|
limit: Type.Optional(Type.Number()),
|
||||||
maxChars: Type.Optional(Type.Number()),
|
maxChars: Type.Optional(Type.Number()),
|
||||||
format: Type.Optional(Type.Union([Type.Literal("aria"), Type.Literal("ai")])),
|
format: optionalStringEnum(BROWSER_SNAPSHOT_FORMATS),
|
||||||
interactive: Type.Optional(Type.Boolean()),
|
interactive: Type.Optional(Type.Boolean()),
|
||||||
compact: Type.Optional(Type.Boolean()),
|
compact: Type.Optional(Type.Boolean()),
|
||||||
depth: Type.Optional(Type.Number()),
|
depth: Type.Optional(Type.Number()),
|
||||||
@@ -128,7 +126,7 @@ const BrowserToolSchema = Type.Object({
|
|||||||
fullPage: Type.Optional(Type.Boolean()),
|
fullPage: Type.Optional(Type.Boolean()),
|
||||||
ref: Type.Optional(Type.String()),
|
ref: Type.Optional(Type.String()),
|
||||||
element: Type.Optional(Type.String()),
|
element: Type.Optional(Type.String()),
|
||||||
type: Type.Optional(Type.Union([Type.Literal("png"), Type.Literal("jpeg")])),
|
type: optionalStringEnum(BROWSER_IMAGE_TYPES),
|
||||||
level: Type.Optional(Type.String()),
|
level: Type.Optional(Type.String()),
|
||||||
paths: Type.Optional(Type.Array(Type.String())),
|
paths: Type.Optional(Type.Array(Type.String())),
|
||||||
inputRef: Type.Optional(Type.String()),
|
inputRef: Type.Optional(Type.String()),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
parseCanvasSnapshotPayload,
|
parseCanvasSnapshotPayload,
|
||||||
} from "../../cli/nodes-canvas.js";
|
} from "../../cli/nodes-canvas.js";
|
||||||
import { imageMimeFromFormat } from "../../media/mime.js";
|
import { imageMimeFromFormat } from "../../media/mime.js";
|
||||||
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||||
import {
|
import {
|
||||||
type AnyAgentTool,
|
type AnyAgentTool,
|
||||||
imageResult,
|
imageResult,
|
||||||
@@ -17,76 +18,44 @@ import {
|
|||||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||||
import { resolveNodeId } from "./nodes-utils.js";
|
import { resolveNodeId } from "./nodes-utils.js";
|
||||||
|
|
||||||
const CanvasToolSchema = Type.Union([
|
const CANVAS_ACTIONS = [
|
||||||
Type.Object({
|
"present",
|
||||||
action: Type.Literal("present"),
|
"hide",
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
"navigate",
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
"eval",
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
"snapshot",
|
||||||
node: Type.Optional(Type.String()),
|
"a2ui_push",
|
||||||
target: Type.Optional(Type.String()),
|
"a2ui_reset",
|
||||||
x: Type.Optional(Type.Number()),
|
] as const;
|
||||||
y: Type.Optional(Type.Number()),
|
|
||||||
width: Type.Optional(Type.Number()),
|
const CANVAS_SNAPSHOT_FORMATS = ["png", "jpg", "jpeg"] as const;
|
||||||
height: Type.Optional(Type.Number()),
|
|
||||||
}),
|
// Flattened schema: runtime validates per-action requirements.
|
||||||
Type.Object({
|
const CanvasToolSchema = Type.Object({
|
||||||
action: Type.Literal("hide"),
|
action: stringEnum(CANVAS_ACTIONS),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
gatewayUrl: Type.Optional(Type.String()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
gatewayToken: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
node: Type.Optional(Type.String()),
|
node: Type.Optional(Type.String()),
|
||||||
}),
|
// present
|
||||||
Type.Object({
|
target: Type.Optional(Type.String()),
|
||||||
action: Type.Literal("navigate"),
|
x: Type.Optional(Type.Number()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
y: Type.Optional(Type.Number()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
width: Type.Optional(Type.Number()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
height: Type.Optional(Type.Number()),
|
||||||
node: Type.Optional(Type.String()),
|
// navigate
|
||||||
url: Type.String(),
|
url: Type.Optional(Type.String()),
|
||||||
}),
|
// eval
|
||||||
Type.Object({
|
javaScript: Type.Optional(Type.String()),
|
||||||
action: Type.Literal("eval"),
|
// snapshot
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
format: optionalStringEnum(CANVAS_SNAPSHOT_FORMATS),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
maxWidth: Type.Optional(Type.Number()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
quality: Type.Optional(Type.Number()),
|
||||||
node: Type.Optional(Type.String()),
|
delayMs: Type.Optional(Type.Number()),
|
||||||
javaScript: Type.String(),
|
// a2ui_push
|
||||||
}),
|
jsonl: Type.Optional(Type.String()),
|
||||||
Type.Object({
|
jsonlPath: Type.Optional(Type.String()),
|
||||||
action: Type.Literal("snapshot"),
|
});
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.Optional(Type.String()),
|
|
||||||
format: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("png"),
|
|
||||||
Type.Literal("jpg"),
|
|
||||||
Type.Literal("jpeg"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
maxWidth: Type.Optional(Type.Number()),
|
|
||||||
quality: Type.Optional(Type.Number()),
|
|
||||||
delayMs: Type.Optional(Type.Number()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("a2ui_push"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.Optional(Type.String()),
|
|
||||||
jsonl: Type.Optional(Type.String()),
|
|
||||||
jsonlPath: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("a2ui_reset"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function createCanvasTool(): AnyAgentTool {
|
export function createCanvasTool(): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,89 +3,42 @@ import {
|
|||||||
normalizeCronJobCreate,
|
normalizeCronJobCreate,
|
||||||
normalizeCronJobPatch,
|
normalizeCronJobPatch,
|
||||||
} from "../../cron/normalize.js";
|
} from "../../cron/normalize.js";
|
||||||
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||||
|
|
||||||
// NOTE: We use Type.Object({}, { additionalProperties: true }) for job/patch
|
// NOTE: We use Type.Object({}, { additionalProperties: true }) for job/patch
|
||||||
// instead of CronAddParamsSchema/CronJobPatchSchema because:
|
// instead of CronAddParamsSchema/CronJobPatchSchema because the gateway schemas
|
||||||
//
|
// contain nested unions. Tool schemas need to stay provider-friendly, so we
|
||||||
// 1. CronAddParamsSchema contains nested Type.Union (for schedule, payload, etc.)
|
// accept "any object" here and validate at runtime.
|
||||||
// 2. TypeBox compiles Type.Union to JSON Schema `anyOf`
|
|
||||||
// 3. pi-ai's sanitizeSchemaForGoogle() strips `anyOf` from nested properties
|
|
||||||
// 4. This leaves empty schemas `{}` which Claude rejects as invalid
|
|
||||||
//
|
|
||||||
// The actual validation happens at runtime via normalizeCronJobCreate/Patch
|
|
||||||
// and the gateway's validateCronAddParams. This schema just needs to accept
|
|
||||||
// any object so the AI can pass through the job definition.
|
|
||||||
//
|
|
||||||
// See: https://github.com/anthropics/anthropic-cookbook/blob/main/misc/tool_use_best_practices.md
|
|
||||||
// Claude requires valid JSON Schema 2020-12 with explicit types.
|
|
||||||
|
|
||||||
const CronToolSchema = Type.Union([
|
const CRON_ACTIONS = [
|
||||||
Type.Object({
|
"status",
|
||||||
action: Type.Literal("status"),
|
"list",
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
"add",
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
"update",
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
"remove",
|
||||||
}),
|
"run",
|
||||||
Type.Object({
|
"runs",
|
||||||
action: Type.Literal("list"),
|
"wake",
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
] as const;
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
const CRON_WAKE_MODES = ["now", "next-heartbeat"] as const;
|
||||||
includeDisabled: Type.Optional(Type.Boolean()),
|
|
||||||
}),
|
// Flattened schema: runtime validates per-action requirements.
|
||||||
Type.Object({
|
const CronToolSchema = Type.Object({
|
||||||
action: Type.Literal("add"),
|
action: stringEnum(CRON_ACTIONS),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
gatewayUrl: Type.Optional(Type.String()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
gatewayToken: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
job: Type.Object({}, { additionalProperties: true }),
|
includeDisabled: Type.Optional(Type.Boolean()),
|
||||||
}),
|
job: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||||
Type.Object({
|
jobId: Type.Optional(Type.String()),
|
||||||
action: Type.Literal("update"),
|
id: Type.Optional(Type.String()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
text: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
mode: optionalStringEnum(CRON_WAKE_MODES),
|
||||||
jobId: Type.Optional(Type.String()),
|
});
|
||||||
id: Type.Optional(Type.String()),
|
|
||||||
patch: Type.Object({}, { additionalProperties: true }),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("remove"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
jobId: Type.Optional(Type.String()),
|
|
||||||
id: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("run"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
jobId: Type.Optional(Type.String()),
|
|
||||||
id: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("runs"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
jobId: Type.Optional(Type.String()),
|
|
||||||
id: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("wake"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
text: Type.String(),
|
|
||||||
mode: Type.Optional(
|
|
||||||
Type.Union([Type.Literal("now"), Type.Literal("next-heartbeat")]),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function createCronTool(): AnyAgentTool {
|
export function createCronTool(): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
function readParentIdParam(
|
function readParentIdParam(
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
): string | null | undefined {
|
): string | null | undefined {
|
||||||
|
if (params.clearParent === true) return null;
|
||||||
if (params.parentId === null) return null;
|
if (params.parentId === null) return null;
|
||||||
return readStringParam(params, "parentId");
|
return readStringParam(params, "parentId");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,26 @@ describe("handleDiscordGuildAction - channel management", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clears the channel parent when clearParent is true", async () => {
|
||||||
|
await handleDiscordGuildAction(
|
||||||
|
"channelEdit",
|
||||||
|
{
|
||||||
|
channelId: "C1",
|
||||||
|
clearParent: true,
|
||||||
|
},
|
||||||
|
channelsEnabled,
|
||||||
|
);
|
||||||
|
expect(editChannelDiscord).toHaveBeenCalledWith({
|
||||||
|
channelId: "C1",
|
||||||
|
name: undefined,
|
||||||
|
topic: undefined,
|
||||||
|
position: undefined,
|
||||||
|
parentId: null,
|
||||||
|
nsfw: undefined,
|
||||||
|
rateLimitPerUser: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("deletes a channel", async () => {
|
it("deletes a channel", async () => {
|
||||||
await handleDiscordGuildAction(
|
await handleDiscordGuildAction(
|
||||||
"channelDelete",
|
"channelDelete",
|
||||||
@@ -264,6 +284,24 @@ describe("handleDiscordGuildAction - channel management", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clears the channel parent on move when clearParent is true", async () => {
|
||||||
|
await handleDiscordGuildAction(
|
||||||
|
"channelMove",
|
||||||
|
{
|
||||||
|
guildId: "G1",
|
||||||
|
channelId: "C1",
|
||||||
|
clearParent: true,
|
||||||
|
},
|
||||||
|
channelsEnabled,
|
||||||
|
);
|
||||||
|
expect(moveChannelDiscord).toHaveBeenCalledWith({
|
||||||
|
guildId: "G1",
|
||||||
|
channelId: "C1",
|
||||||
|
parentId: null,
|
||||||
|
position: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("creates a category with type=4", async () => {
|
it("creates a category with type=4", async () => {
|
||||||
await handleDiscordGuildAction(
|
await handleDiscordGuildAction(
|
||||||
"categoryCreate",
|
"categoryCreate",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
type RestartSentinelPayload,
|
type RestartSentinelPayload,
|
||||||
writeRestartSentinel,
|
writeRestartSentinel,
|
||||||
} from "../../infra/restart-sentinel.js";
|
} from "../../infra/restart-sentinel.js";
|
||||||
|
import { stringEnum } from "../schema/typebox.js";
|
||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
import { callGatewayTool } from "./gateway.js";
|
import { callGatewayTool } from "./gateway.js";
|
||||||
|
|
||||||
@@ -18,16 +19,11 @@ const GATEWAY_ACTIONS = [
|
|||||||
"update.run",
|
"update.run",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type GatewayAction = (typeof GATEWAY_ACTIONS)[number];
|
|
||||||
|
|
||||||
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
||||||
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
||||||
// The discriminator (action) determines which properties are relevant; runtime validates.
|
// The discriminator (action) determines which properties are relevant; runtime validates.
|
||||||
const GatewayToolSchema = Type.Object({
|
const GatewayToolSchema = Type.Object({
|
||||||
action: Type.Unsafe<GatewayAction>({
|
action: stringEnum(GATEWAY_ACTIONS),
|
||||||
type: "string",
|
|
||||||
enum: [...GATEWAY_ACTIONS],
|
|
||||||
}),
|
|
||||||
// restart
|
// restart
|
||||||
delayMs: Type.Optional(Type.Number()),
|
delayMs: Type.Optional(Type.Number()),
|
||||||
reason: Type.Optional(Type.String()),
|
reason: Type.Optional(Type.String()),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
type ProviderMessageActionName,
|
type ProviderMessageActionName,
|
||||||
} from "../../providers/plugins/types.js";
|
} from "../../providers/plugins/types.js";
|
||||||
import { normalizeAccountId } from "../../routing/session-key.js";
|
import { normalizeAccountId } from "../../routing/session-key.js";
|
||||||
|
import { stringEnum } from "../schema/typebox.js";
|
||||||
import type { AnyAgentTool } from "./common.js";
|
import type { AnyAgentTool } from "./common.js";
|
||||||
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
||||||
|
|
||||||
@@ -90,12 +91,17 @@ const MessageToolCommonSchema = {
|
|||||||
timeoutMs: Type.Optional(Type.Number()),
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
name: Type.Optional(Type.String()),
|
name: Type.Optional(Type.String()),
|
||||||
type: Type.Optional(Type.Number()),
|
type: Type.Optional(Type.Number()),
|
||||||
parentId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
parentId: Type.Optional(Type.String()),
|
||||||
topic: Type.Optional(Type.String()),
|
topic: Type.Optional(Type.String()),
|
||||||
position: Type.Optional(Type.Number()),
|
position: Type.Optional(Type.Number()),
|
||||||
nsfw: Type.Optional(Type.Boolean()),
|
nsfw: Type.Optional(Type.Boolean()),
|
||||||
rateLimitPerUser: Type.Optional(Type.Number()),
|
rateLimitPerUser: Type.Optional(Type.Number()),
|
||||||
categoryId: Type.Optional(Type.String()),
|
categoryId: Type.Optional(Type.String()),
|
||||||
|
clearParent: Type.Optional(
|
||||||
|
Type.Boolean({
|
||||||
|
description: "Clear the parent/category when supported by the provider.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildMessageToolSchemaFromActions(
|
function buildMessageToolSchemaFromActions(
|
||||||
@@ -105,31 +111,10 @@ function buildMessageToolSchemaFromActions(
|
|||||||
const props: Record<string, unknown> = { ...MessageToolCommonSchema };
|
const props: Record<string, unknown> = { ...MessageToolCommonSchema };
|
||||||
if (!options.includeButtons) delete props.buttons;
|
if (!options.includeButtons) delete props.buttons;
|
||||||
|
|
||||||
const schemas: Array<ReturnType<typeof Type.Object>> = [];
|
return Type.Object({
|
||||||
if (actions.includes("send")) {
|
action: stringEnum(actions),
|
||||||
schemas.push(
|
...props,
|
||||||
Type.Object({
|
});
|
||||||
action: Type.Literal("send"),
|
|
||||||
to: Type.String(),
|
|
||||||
message: Type.String(),
|
|
||||||
...props,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonSendActions = actions.filter((action) => action !== "send");
|
|
||||||
if (nonSendActions.length > 0) {
|
|
||||||
schemas.push(
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Union(
|
|
||||||
nonSendActions.map((action) => Type.Literal(action)),
|
|
||||||
),
|
|
||||||
...props,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return schemas.length === 1 ? schemas[0] : Type.Union(schemas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
|
const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
|
||||||
|
|||||||
@@ -18,151 +18,73 @@ import {
|
|||||||
} from "../../cli/nodes-screen.js";
|
} from "../../cli/nodes-screen.js";
|
||||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||||
import { imageMimeFromFormat } from "../../media/mime.js";
|
import { imageMimeFromFormat } from "../../media/mime.js";
|
||||||
|
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||||
import { sanitizeToolResultImages } from "../tool-images.js";
|
import { sanitizeToolResultImages } from "../tool-images.js";
|
||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||||
import { resolveNodeId } from "./nodes-utils.js";
|
import { resolveNodeId } from "./nodes-utils.js";
|
||||||
|
|
||||||
const NodesToolSchema = Type.Union([
|
const NODES_TOOL_ACTIONS = [
|
||||||
Type.Object({
|
"status",
|
||||||
action: Type.Literal("status"),
|
"describe",
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
"pending",
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
"approve",
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
"reject",
|
||||||
|
"notify",
|
||||||
|
"camera_snap",
|
||||||
|
"camera_list",
|
||||||
|
"camera_clip",
|
||||||
|
"screen_record",
|
||||||
|
"location_get",
|
||||||
|
"run",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const NOTIFY_PRIORITIES = ["passive", "active", "timeSensitive"] as const;
|
||||||
|
const NOTIFY_DELIVERIES = ["system", "overlay", "auto"] as const;
|
||||||
|
const CAMERA_FACING = ["front", "back", "both"] as const;
|
||||||
|
const LOCATION_ACCURACY = ["coarse", "balanced", "precise"] as const;
|
||||||
|
|
||||||
|
// Flattened schema: runtime validates per-action requirements.
|
||||||
|
const NodesToolSchema = Type.Object({
|
||||||
|
action: stringEnum(NODES_TOOL_ACTIONS),
|
||||||
|
gatewayUrl: Type.Optional(Type.String()),
|
||||||
|
gatewayToken: Type.Optional(Type.String()),
|
||||||
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
|
node: Type.Optional(Type.String()),
|
||||||
|
requestId: Type.Optional(Type.String()),
|
||||||
|
// notify
|
||||||
|
title: Type.Optional(Type.String()),
|
||||||
|
body: Type.Optional(Type.String()),
|
||||||
|
sound: Type.Optional(Type.String()),
|
||||||
|
priority: optionalStringEnum(NOTIFY_PRIORITIES),
|
||||||
|
delivery: optionalStringEnum(NOTIFY_DELIVERIES),
|
||||||
|
// camera_snap / camera_clip
|
||||||
|
facing: optionalStringEnum(CAMERA_FACING, {
|
||||||
|
description: "camera_snap: front/back/both; camera_clip: front/back only.",
|
||||||
}),
|
}),
|
||||||
Type.Object({
|
maxWidth: Type.Optional(Type.Number()),
|
||||||
action: Type.Literal("describe"),
|
quality: Type.Optional(Type.Number()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
delayMs: Type.Optional(Type.Number()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
deviceId: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
duration: Type.Optional(Type.String()),
|
||||||
node: Type.String(),
|
durationMs: Type.Optional(Type.Number()),
|
||||||
}),
|
includeAudio: Type.Optional(Type.Boolean()),
|
||||||
Type.Object({
|
// screen_record
|
||||||
action: Type.Literal("pending"),
|
fps: Type.Optional(Type.Number()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
screenIndex: Type.Optional(Type.Number()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
outPath: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
// location_get
|
||||||
}),
|
maxAgeMs: Type.Optional(Type.Number()),
|
||||||
Type.Object({
|
locationTimeoutMs: Type.Optional(Type.Number()),
|
||||||
action: Type.Literal("approve"),
|
desiredAccuracy: optionalStringEnum(LOCATION_ACCURACY),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
// run
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
command: Type.Optional(Type.Array(Type.String())),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
cwd: Type.Optional(Type.String()),
|
||||||
requestId: Type.String(),
|
env: Type.Optional(Type.Array(Type.String())),
|
||||||
}),
|
commandTimeoutMs: Type.Optional(Type.Number()),
|
||||||
Type.Object({
|
invokeTimeoutMs: Type.Optional(Type.Number()),
|
||||||
action: Type.Literal("reject"),
|
needsScreenRecording: Type.Optional(Type.Boolean()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
});
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
requestId: Type.String(),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("notify"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
title: Type.Optional(Type.String()),
|
|
||||||
body: Type.Optional(Type.String()),
|
|
||||||
sound: Type.Optional(Type.String()),
|
|
||||||
priority: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("passive"),
|
|
||||||
Type.Literal("active"),
|
|
||||||
Type.Literal("timeSensitive"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
delivery: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("system"),
|
|
||||||
Type.Literal("overlay"),
|
|
||||||
Type.Literal("auto"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("camera_snap"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
facing: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("front"),
|
|
||||||
Type.Literal("back"),
|
|
||||||
Type.Literal("both"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
maxWidth: Type.Optional(Type.Number()),
|
|
||||||
quality: Type.Optional(Type.Number()),
|
|
||||||
delayMs: Type.Optional(Type.Number()),
|
|
||||||
deviceId: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("camera_list"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("camera_clip"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
facing: Type.Optional(
|
|
||||||
Type.Union([Type.Literal("front"), Type.Literal("back")]),
|
|
||||||
),
|
|
||||||
duration: Type.Optional(Type.String()),
|
|
||||||
durationMs: Type.Optional(Type.Number()),
|
|
||||||
includeAudio: Type.Optional(Type.Boolean()),
|
|
||||||
deviceId: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("screen_record"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
duration: Type.Optional(Type.String()),
|
|
||||||
durationMs: Type.Optional(Type.Number()),
|
|
||||||
fps: Type.Optional(Type.Number()),
|
|
||||||
screenIndex: Type.Optional(Type.Number()),
|
|
||||||
includeAudio: Type.Optional(Type.Boolean()),
|
|
||||||
outPath: Type.Optional(Type.String()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("location_get"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
maxAgeMs: Type.Optional(Type.Number()),
|
|
||||||
locationTimeoutMs: Type.Optional(Type.Number()),
|
|
||||||
desiredAccuracy: Type.Optional(
|
|
||||||
Type.Union([
|
|
||||||
Type.Literal("coarse"),
|
|
||||||
Type.Literal("balanced"),
|
|
||||||
Type.Literal("precise"),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("run"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
node: Type.String(),
|
|
||||||
command: Type.Array(Type.String()),
|
|
||||||
cwd: Type.Optional(Type.String()),
|
|
||||||
env: Type.Optional(Type.Array(Type.String())),
|
|
||||||
commandTimeoutMs: Type.Optional(Type.Number()),
|
|
||||||
invokeTimeoutMs: Type.Optional(Type.Number()),
|
|
||||||
needsScreenRecording: Type.Optional(Type.Boolean()),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function createNodesTool(): AnyAgentTool {
|
export function createNodesTool(): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import type { GatewayMessageProvider } from "../../utils/message-provider.js";
|
import type { GatewayMessageProvider } from "../../utils/message-provider.js";
|
||||||
import { resolveAgentConfig } from "../agent-scope.js";
|
import { resolveAgentConfig } from "../agent-scope.js";
|
||||||
import { AGENT_LANE_SUBAGENT } from "../lanes.js";
|
import { AGENT_LANE_SUBAGENT } from "../lanes.js";
|
||||||
|
import { optionalStringEnum } from "../schema/typebox.js";
|
||||||
import { buildSubagentSystemPrompt } from "../subagent-announce.js";
|
import { buildSubagentSystemPrompt } from "../subagent-announce.js";
|
||||||
import { registerSubagentRun } from "../subagent-registry.js";
|
import { registerSubagentRun } from "../subagent-registry.js";
|
||||||
import type { AnyAgentTool } from "./common.js";
|
import type { AnyAgentTool } from "./common.js";
|
||||||
@@ -30,9 +31,7 @@ const SessionsSpawnToolSchema = Type.Object({
|
|||||||
runTimeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
|
runTimeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
|
||||||
// Back-compat alias. Prefer runTimeoutSeconds.
|
// Back-compat alias. Prefer runTimeoutSeconds.
|
||||||
timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
|
timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
|
||||||
cleanup: Type.Optional(
|
cleanup: optionalStringEnum(["delete", "keep"] as const),
|
||||||
Type.Union([Type.Literal("delete"), Type.Literal("keep")]),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function normalizeModelSelection(value: unknown): string | undefined {
|
function normalizeModelSelection(value: unknown): string | undefined {
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ import type {
|
|||||||
|
|
||||||
const providerId = "discord";
|
const providerId = "discord";
|
||||||
|
|
||||||
|
function readParentIdParam(
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
): string | null | undefined {
|
||||||
|
if (params.clearParent === true) return null;
|
||||||
|
if (params.parentId === null) return null;
|
||||||
|
return readStringParam(params, "parentId");
|
||||||
|
}
|
||||||
|
|
||||||
export const discordMessageActions: ProviderMessageActionAdapter = {
|
export const discordMessageActions: ProviderMessageActionAdapter = {
|
||||||
listActions: ({ cfg }) => {
|
listActions: ({ cfg }) => {
|
||||||
const accounts = listEnabledDiscordAccounts(cfg).filter(
|
const accounts = listEnabledDiscordAccounts(cfg).filter(
|
||||||
@@ -462,8 +470,7 @@ export const discordMessageActions: ProviderMessageActionAdapter = {
|
|||||||
const guildId = readStringParam(params, "guildId", { required: true });
|
const guildId = readStringParam(params, "guildId", { required: true });
|
||||||
const name = readStringParam(params, "name", { required: true });
|
const name = readStringParam(params, "name", { required: true });
|
||||||
const type = readNumberParam(params, "type", { integer: true });
|
const type = readNumberParam(params, "type", { integer: true });
|
||||||
const parentId =
|
const parentId = readParentIdParam(params);
|
||||||
params.parentId === null ? null : readStringParam(params, "parentId");
|
|
||||||
const topic = readStringParam(params, "topic");
|
const topic = readStringParam(params, "topic");
|
||||||
const position = readNumberParam(params, "position", { integer: true });
|
const position = readNumberParam(params, "position", { integer: true });
|
||||||
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
|
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
|
||||||
@@ -489,8 +496,7 @@ export const discordMessageActions: ProviderMessageActionAdapter = {
|
|||||||
const name = readStringParam(params, "name");
|
const name = readStringParam(params, "name");
|
||||||
const topic = readStringParam(params, "topic");
|
const topic = readStringParam(params, "topic");
|
||||||
const position = readNumberParam(params, "position", { integer: true });
|
const position = readNumberParam(params, "position", { integer: true });
|
||||||
const parentId =
|
const parentId = readParentIdParam(params);
|
||||||
params.parentId === null ? null : readStringParam(params, "parentId");
|
|
||||||
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
|
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
|
||||||
const rateLimitPerUser = readNumberParam(params, "rateLimitPerUser", {
|
const rateLimitPerUser = readNumberParam(params, "rateLimitPerUser", {
|
||||||
integer: true,
|
integer: true,
|
||||||
@@ -525,8 +531,7 @@ export const discordMessageActions: ProviderMessageActionAdapter = {
|
|||||||
const channelId = readStringParam(params, "channelId", {
|
const channelId = readStringParam(params, "channelId", {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
const parentId =
|
const parentId = readParentIdParam(params);
|
||||||
params.parentId === null ? null : readStringParam(params, "parentId");
|
|
||||||
const position = readNumberParam(params, "position", { integer: true });
|
const position = readNumberParam(params, "position", { integer: true });
|
||||||
return await handleDiscordAction(
|
return await handleDiscordAction(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user