refactor(core): dedupe schema and command parsing helpers

This commit is contained in:
Peter Steinberger
2026-02-16 23:47:48 +00:00
parent c55e017c19
commit 4088c0b89d
3 changed files with 71 additions and 109 deletions

View File

@@ -207,6 +207,36 @@ function simplifyUnionVariants(params: { obj: Record<string, unknown>; variants:
return { variants: stripped ? nonNullVariants : variants };
}
function flattenUnionFallback(
obj: Record<string, unknown>,
value: unknown,
): Record<string, unknown> | undefined {
if (!Array.isArray(value)) {
return undefined;
}
const variants = (value as Record<string, unknown>[]).filter((v) => v && typeof v === "object");
const types = new Set(variants.map((v) => v.type).filter(Boolean));
if (variants.length === 1) {
const merged: Record<string, unknown> = { ...variants[0] };
copySchemaMeta(obj, merged);
return merged;
}
if (types.size === 1) {
const merged: Record<string, unknown> = { type: Array.from(types)[0] };
copySchemaMeta(obj, merged);
return merged;
}
const first = variants[0];
if (first?.type) {
const merged: Record<string, unknown> = { type: first.type };
copySchemaMeta(obj, merged);
return merged;
}
const merged: Record<string, unknown> = {};
copySchemaMeta(obj, merged);
return merged;
}
function cleanSchemaForGeminiWithDefs(
schema: unknown,
defs: SchemaDefs | undefined,
@@ -343,58 +373,14 @@ function cleanSchemaForGeminiWithDefs(
// If simplifyUnionVariants couldn't reduce the union above, flatten it
// here as a fallback: pick the first variant's type or use a permissive
// schema so the tool declaration is accepted.
if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) {
const variants = (cleaned.anyOf as Record<string, unknown>[]).filter(
(v) => v && typeof v === "object",
);
const types = new Set(variants.map((v) => v.type).filter(Boolean));
if (variants.length === 1) {
const merged: Record<string, unknown> = { ...variants[0] };
copySchemaMeta(cleaned, merged);
return merged;
}
if (types.size === 1) {
const merged: Record<string, unknown> = { type: Array.from(types)[0] };
copySchemaMeta(cleaned, merged);
return merged;
}
// Mixed types (e.g. string | array<string>): use first variant's type.
// The execute function already handles type coercion at runtime.
const first = variants[0];
if (first?.type) {
const merged: Record<string, unknown> = { type: first.type };
copySchemaMeta(cleaned, merged);
return merged;
}
const merged: Record<string, unknown> = {};
copySchemaMeta(cleaned, merged);
return merged;
const flattenedAnyOf = flattenUnionFallback(cleaned, cleaned.anyOf);
if (flattenedAnyOf) {
return flattenedAnyOf;
}
if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) {
const variants = (cleaned.oneOf as Record<string, unknown>[]).filter(
(v) => v && typeof v === "object",
);
const types = new Set(variants.map((v) => v.type).filter(Boolean));
if (variants.length === 1) {
const merged: Record<string, unknown> = { ...variants[0] };
copySchemaMeta(cleaned, merged);
return merged;
}
if (types.size === 1) {
const merged: Record<string, unknown> = { type: Array.from(types)[0] };
copySchemaMeta(cleaned, merged);
return merged;
}
const first = variants[0];
if (first?.type) {
const merged: Record<string, unknown> = { type: first.type };
copySchemaMeta(cleaned, merged);
return merged;
}
const merged: Record<string, unknown> = {};
copySchemaMeta(cleaned, merged);
return merged;
const flattenedOneOf = flattenUnionFallback(cleaned, cleaned.oneOf);
if (flattenedOneOf) {
return flattenedOneOf;
}
return cleaned;

View File

@@ -483,7 +483,10 @@ function unwrapShellWrapper(command: string): string {
return inner ? (stripOuterQuotes(inner) ?? command) : command;
}
function firstTopLevelStage(command: string): string {
function scanTopLevelChars(
command: string,
visit: (char: string, index: number) => boolean | void,
): void {
let quote: '"' | "'" | undefined;
let escaped = false;
@@ -511,52 +514,39 @@ function firstTopLevelStage(command: string): string {
continue;
}
if (char === ";") {
return command.slice(0, i);
}
if ((char === "&" || char === "|") && command[i + 1] === char) {
return command.slice(0, i);
if (visit(char, i) === false) {
return;
}
}
}
return command;
function firstTopLevelStage(command: string): string {
let splitIndex = -1;
scanTopLevelChars(command, (char, index) => {
if (char === ";") {
splitIndex = index;
return false;
}
if ((char === "&" || char === "|") && command[index + 1] === char) {
splitIndex = index;
return false;
}
return true;
});
return splitIndex >= 0 ? command.slice(0, splitIndex) : command;
}
function splitTopLevelPipes(command: string): string[] {
const parts: string[] = [];
let quote: '"' | "'" | undefined;
let escaped = false;
let start = 0;
for (let i = 0; i < command.length; i += 1) {
const char = command[i];
if (escaped) {
escaped = false;
continue;
scanTopLevelChars(command, (char, index) => {
if (char === "|" && command[index - 1] !== "|" && command[index + 1] !== "|") {
parts.push(command.slice(start, index));
start = index + 1;
}
if (char === "\\") {
escaped = true;
continue;
}
if (quote) {
if (char === quote) {
quote = undefined;
}
continue;
}
if (char === '"' || char === "'") {
quote = char;
continue;
}
if (char === "|" && command[i - 1] !== "|" && command[i + 1] !== "|") {
parts.push(command.slice(start, i));
start = i + 1;
}
}
return true;
});
parts.push(command.slice(start));
return parts.map((part) => part.trim()).filter((part) => part.length > 0);

View File

@@ -18,11 +18,9 @@ type SelectOption = {
text?: { text?: string };
};
type InteractionSummary = {
interactionType?: "block_action" | "view_submission" | "view_closed";
actionId: string;
blockId?: string;
type InteractionSelectionFields = {
actionType?: string;
blockId?: string;
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
value?: string;
selectedValues?: string[];
@@ -39,6 +37,11 @@ type InteractionSummary = {
inputUrl?: string;
richTextValue?: unknown;
richTextPreview?: string;
};
type InteractionSummary = InteractionSelectionFields & {
interactionType?: "block_action" | "view_submission" | "view_closed";
actionId: string;
userId?: string;
teamId?: string;
triggerId?: string;
@@ -50,26 +53,9 @@ type InteractionSummary = {
threadTs?: string;
};
type ModalInputSummary = {
type ModalInputSummary = InteractionSelectionFields & {
blockId: string;
actionId: string;
actionType?: string;
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
value?: string;
selectedValues?: string[];
selectedUsers?: string[];
selectedChannels?: string[];
selectedConversations?: string[];
selectedLabels?: string[];
selectedDate?: string;
selectedTime?: string;
selectedDateTime?: number;
inputValue?: string;
inputNumber?: number;
inputEmail?: string;
inputUrl?: string;
richTextValue?: unknown;
richTextPreview?: string;
};
function readOptionValues(options: unknown): string[] | undefined {