Merge pull request #3348 from code-yeongyu/refactor/ulw-repo-cleanup-20260411
refactor: simplify nullish guards and remove dead no-op paths
This commit is contained in:
@@ -79,11 +79,7 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
|
||||
|
||||
const normalizedPlugins = [...otherPlugins]
|
||||
|
||||
if (canonicalEntries.length > 0 || legacyEntries.length > 0) {
|
||||
normalizedPlugins.push(pluginEntry)
|
||||
} else {
|
||||
normalizedPlugins.push(pluginEntry)
|
||||
}
|
||||
normalizedPlugins.push(pluginEntry)
|
||||
|
||||
config.plugin = normalizedPlugins
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
|
||||
import { parseOpenCodeConfigFileWithError } from "./parse-opencode-config-file"
|
||||
|
||||
describe("parseOpenCodeConfigFileWithError", () => {
|
||||
const tempDirectories: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of tempDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test("#given a valid object config #when parsing the file #then it returns the parsed config", () => {
|
||||
// given
|
||||
const directory = mkdtempSync(join(tmpdir(), "omo-parse-config-"))
|
||||
tempDirectories.push(directory)
|
||||
const filePath = join(directory, "opencode.json")
|
||||
writeFileSync(filePath, '{"plugin": ["oh-my-openagent"]}\n', "utf-8")
|
||||
|
||||
// when
|
||||
const result = parseOpenCodeConfigFileWithError(filePath)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
config: { plugin: ["oh-my-openagent"] },
|
||||
})
|
||||
})
|
||||
|
||||
test("#given a null config payload #when parsing the file #then it returns a null parse error", () => {
|
||||
// given
|
||||
const directory = mkdtempSync(join(tmpdir(), "omo-parse-config-"))
|
||||
tempDirectories.push(directory)
|
||||
const filePath = join(directory, "opencode.json")
|
||||
writeFileSync(filePath, "null\n", "utf-8")
|
||||
|
||||
// when
|
||||
const result = parseOpenCodeConfigFileWithError(filePath)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
config: null,
|
||||
error: `Config file parsed to null/undefined: ${filePath}. Ensure it contains valid JSON.`,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -30,7 +30,7 @@ export function parseOpenCodeConfigFileWithError(path: string): ParseConfigResul
|
||||
|
||||
const config = parseJsonc<OpenCodeConfig>(content)
|
||||
|
||||
if (config === null || config === undefined) {
|
||||
if (config == null) {
|
||||
return { config: null, error: `Config file parsed to null/undefined: ${path}. Ensure it contains valid JSON.` }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const DynamicContextPruningConfigSchema = z.object({
|
||||
/** Enable dynamic context pruning (default: false) */
|
||||
enabled: z.boolean().default(false),
|
||||
/** Notification level: off, minimal, or detailed (default: detailed) */
|
||||
notification: z.enum(["off", "minimal", "detailed"]).default("detailed"),
|
||||
/** Turn protection - prevent pruning recent tool outputs */
|
||||
turn_protection: z
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const RalphLoopConfigSchema = z.object({
|
||||
/** Enable ralph loop functionality (default: false - opt-in feature) */
|
||||
enabled: z.boolean().default(false),
|
||||
/** Default max iterations if not specified in command (default: 100) */
|
||||
default_max_iterations: z.number().min(1).max(1000).default(100),
|
||||
/** Custom state file directory relative to project root (default: .opencode/) */
|
||||
state_dir: z.string().optional(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const StartWorkConfigSchema = z.object({
|
||||
/** Enable auto-commit after each atomic task completion (default: true) */
|
||||
auto_commit: z.boolean().default(true),
|
||||
})
|
||||
|
||||
|
||||
@@ -112,6 +112,20 @@ describe("loop-detector", () => {
|
||||
expect(result).toBe("read")
|
||||
})
|
||||
|
||||
test("#given nullish inputs #when signatures are created #then null and undefined behave the same", () => {
|
||||
// given
|
||||
const undefinedInput = undefined
|
||||
const nullInput = null
|
||||
|
||||
// when
|
||||
const undefinedResult = createToolCallSignature("read", undefinedInput)
|
||||
const nullResult = createToolCallSignature("read", nullInput)
|
||||
|
||||
// then
|
||||
expect(undefinedResult).toBe("read")
|
||||
expect(nullResult).toBe(undefinedResult)
|
||||
})
|
||||
|
||||
test("#given tool with empty object input #when signature created #then returns bare tool name", () => {
|
||||
const result = createToolCallSignature("read", {})
|
||||
|
||||
@@ -259,5 +273,24 @@ describe("loop-detector", () => {
|
||||
expect(result).toEqual({ triggered: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given nullish tool inputs", () => {
|
||||
test("#when recorded #then null and undefined produce the same unknown-input window", () => {
|
||||
// given
|
||||
const settings = resolveCircuitBreakerSettings()
|
||||
|
||||
// when
|
||||
const undefinedWindow = recordToolCall(undefined, "read", settings, undefined)
|
||||
const nullWindow = recordToolCall(undefined, "read", settings, null)
|
||||
|
||||
// then
|
||||
expect(undefinedWindow).toEqual(nullWindow)
|
||||
expect(undefinedWindow).toEqual({
|
||||
lastSignature: "read::__unknown-input__",
|
||||
consecutiveCount: 1,
|
||||
threshold: settings.consecutiveThreshold,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@ export function recordToolCall(
|
||||
settings: CircuitBreakerSettings,
|
||||
toolInput?: Record<string, unknown> | null
|
||||
): ToolCallWindow {
|
||||
if (toolInput === undefined || toolInput === null) {
|
||||
if (toolInput == null) {
|
||||
return {
|
||||
lastSignature: `${toolName}::__unknown-input__`,
|
||||
consecutiveCount: 1,
|
||||
@@ -62,7 +62,7 @@ export function recordToolCall(
|
||||
}
|
||||
|
||||
function sortObject(obj: unknown): unknown {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
if (obj == null) return obj
|
||||
if (typeof obj !== "object") return obj
|
||||
if (Array.isArray(obj)) return obj.map(sortObject)
|
||||
|
||||
@@ -78,7 +78,7 @@ export function createToolCallSignature(
|
||||
toolName: string,
|
||||
toolInput?: Record<string, unknown> | null
|
||||
): string {
|
||||
if (toolInput === undefined || toolInput === null) {
|
||||
if (toolInput == null) {
|
||||
return toolName
|
||||
}
|
||||
if (Object.keys(toolInput).length === 0) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export function expandEnvVars(value: string, options: ExpandEnvVarsOptions = {})
|
||||
}
|
||||
|
||||
export function expandEnvVarsInObject<T>(obj: T, options: ExpandEnvVarsOptions = {}): T {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
if (obj == null) return obj
|
||||
if (typeof obj === "string") return expandEnvVars(obj, options) as T
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => expandEnvVarsInObject(item, options)) as T
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import { resolvePluginPath, resolvePluginPaths } from "./plugin-path-resolver"
|
||||
|
||||
describe("resolvePluginPath", () => {
|
||||
test("#given a plugin root placeholder #when resolving the path #then it replaces the placeholder", () => {
|
||||
// given
|
||||
const path = "${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
||||
|
||||
// when
|
||||
const result = resolvePluginPath(path, "/tmp/plugin-root")
|
||||
|
||||
// then
|
||||
expect(result).toBe("/tmp/plugin-root/dist/index.js")
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolvePluginPaths", () => {
|
||||
test("#given a nested object #when resolving paths #then it rewrites every nested string path", () => {
|
||||
// given
|
||||
const value = {
|
||||
command: "node",
|
||||
args: ["${CLAUDE_PLUGIN_ROOT}/server.js"],
|
||||
nested: {
|
||||
config: "${CLAUDE_PLUGIN_ROOT}/config.json",
|
||||
},
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolvePluginPaths(value, "/tmp/plugin-root")
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
command: "node",
|
||||
args: ["/tmp/plugin-root/server.js"],
|
||||
nested: {
|
||||
config: "/tmp/plugin-root/config.json",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("#given nullish input #when resolving paths #then it returns the same nullish value", () => {
|
||||
// given
|
||||
const nullValue = null
|
||||
const undefinedValue = undefined
|
||||
|
||||
// when
|
||||
const nullResult = resolvePluginPaths(nullValue, "/tmp/plugin-root")
|
||||
const undefinedResult = resolvePluginPaths(undefinedValue, "/tmp/plugin-root")
|
||||
|
||||
// then
|
||||
expect(nullResult).toBeNull()
|
||||
expect(undefinedResult).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,7 @@ export function resolvePluginPath(path: string, pluginRoot: string): string {
|
||||
}
|
||||
|
||||
export function resolvePluginPaths<T>(obj: T, pluginRoot: string): T {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
if (obj == null) return obj
|
||||
if (typeof obj === "string") {
|
||||
return resolvePluginPath(obj, pluginRoot) as T
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ interface ToolExecuteOutput {
|
||||
metadata: unknown;
|
||||
}
|
||||
|
||||
interface ToolExecuteBeforeOutput {
|
||||
args: unknown;
|
||||
interface DirectoryAgentsInjectorHook {
|
||||
"tool.execute.before"?: (input: ToolExecuteInput, output: { args: unknown }) => Promise<void>;
|
||||
"tool.execute.after": (input: ToolExecuteInput, output: ToolExecuteOutput) => Promise<void>;
|
||||
event: (input: EventInput) => Promise<void>;
|
||||
}
|
||||
|
||||
interface EventInput {
|
||||
@@ -30,7 +32,7 @@ interface EventInput {
|
||||
export function createDirectoryAgentsInjectorHook(
|
||||
ctx: PluginInput,
|
||||
modelCacheState?: { anthropicContext1MEnabled: boolean },
|
||||
) {
|
||||
): DirectoryAgentsInjectorHook {
|
||||
const sessionCaches = new Map<string, Set<string>>();
|
||||
const truncator = createDynamicTruncator(ctx, modelCacheState);
|
||||
|
||||
@@ -50,14 +52,6 @@ export function createDirectoryAgentsInjectorHook(
|
||||
}
|
||||
};
|
||||
|
||||
const toolExecuteBefore = async (
|
||||
input: ToolExecuteInput,
|
||||
output: ToolExecuteBeforeOutput,
|
||||
): Promise<void> => {
|
||||
void input;
|
||||
void output;
|
||||
};
|
||||
|
||||
const eventHandler = async ({ event }: EventInput) => {
|
||||
const props = event.properties as Record<string, unknown> | undefined;
|
||||
|
||||
@@ -80,7 +74,6 @@ export function createDirectoryAgentsInjectorHook(
|
||||
};
|
||||
|
||||
return {
|
||||
"tool.execute.before": toolExecuteBefore,
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
event: eventHandler,
|
||||
};
|
||||
|
||||
@@ -16,8 +16,10 @@ interface ToolExecuteOutput {
|
||||
metadata: unknown;
|
||||
}
|
||||
|
||||
interface ToolExecuteBeforeOutput {
|
||||
args: unknown;
|
||||
interface DirectoryReadmeInjectorHook {
|
||||
"tool.execute.before"?: (input: ToolExecuteInput, output: { args: unknown }) => Promise<void>;
|
||||
"tool.execute.after": (input: ToolExecuteInput, output: ToolExecuteOutput) => Promise<void>;
|
||||
event: (input: EventInput) => Promise<void>;
|
||||
}
|
||||
|
||||
interface EventInput {
|
||||
@@ -30,7 +32,7 @@ interface EventInput {
|
||||
export function createDirectoryReadmeInjectorHook(
|
||||
ctx: PluginInput,
|
||||
modelCacheState?: { anthropicContext1MEnabled: boolean },
|
||||
) {
|
||||
): DirectoryReadmeInjectorHook {
|
||||
const sessionCaches = new Map<string, Set<string>>();
|
||||
const truncator = createDynamicTruncator(ctx, modelCacheState);
|
||||
|
||||
@@ -50,14 +52,6 @@ export function createDirectoryReadmeInjectorHook(
|
||||
}
|
||||
};
|
||||
|
||||
const toolExecuteBefore = async (
|
||||
input: ToolExecuteInput,
|
||||
output: ToolExecuteBeforeOutput,
|
||||
): Promise<void> => {
|
||||
void input;
|
||||
void output;
|
||||
};
|
||||
|
||||
const eventHandler = async ({ event }: EventInput) => {
|
||||
const props = event.properties as Record<string, unknown> | undefined;
|
||||
|
||||
@@ -80,7 +74,6 @@ export function createDirectoryReadmeInjectorHook(
|
||||
};
|
||||
|
||||
return {
|
||||
"tool.execute.before": toolExecuteBefore,
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
event: eventHandler,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { normalizeSDKResponse } from "../shared/normalize-sdk-response"
|
||||
import { getSessionPromptParams } from "../shared/session-prompt-params-state"
|
||||
import { getModelCapabilities, resolveCompatibleModelSettings } from "../shared"
|
||||
|
||||
@@ -58,8 +57,6 @@ function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null {
|
||||
? model.id
|
||||
: undefined
|
||||
const providerId = provider.id
|
||||
const variant = message.variant
|
||||
|
||||
if (typeof providerID !== "string") return null
|
||||
if (typeof modelID !== "string") return null
|
||||
if (typeof providerId !== "string") return null
|
||||
@@ -71,7 +68,6 @@ function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null {
|
||||
provider: { id: providerId },
|
||||
message,
|
||||
rawMessage: message,
|
||||
...(typeof variant === "string" ? {} : {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export function normalizeSDKResponse<TData>(
|
||||
fallback: TData,
|
||||
options?: NormalizeSDKResponseOptions,
|
||||
): TData {
|
||||
if (response === null || response === undefined) {
|
||||
if (response == null) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function normalizeSDKResponse<TData>(
|
||||
|
||||
if (typeof response === "object" && "data" in response) {
|
||||
const data = (response as { data?: unknown }).data
|
||||
if (data !== null && data !== undefined) {
|
||||
if (data != null) {
|
||||
return data as TData
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user