From c29a896ac085003381ada66ed4fff5872a8877e9 Mon Sep 17 00:00:00 2001 From: Madina Date: Sat, 29 Aug 2026 21:56:35 +0000 Subject: [PATCH 1/2] Add dev container configuration --- .devcontainer/devcontainer.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..9975cd3f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Node.js & TypeScript", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm", + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers-extra/features/bun:1": { + "version": "1.3.3" + } + } + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From 21dbe6a8b5a252a6a446a94857dabbfab01c69ee Mon Sep 17 00:00:00 2001 From: Madina Date: Fri, 4 Sep 2026 14:57:52 +0000 Subject: [PATCH 2/2] Refactor flattenToolArgs: extract to part-utils and flatten nesting to reduce complexity --- .../src/components/share/part-utils.test.ts | 40 +++++++++++++++++++ .../web/src/components/share/part-utils.ts | 15 +++++++ packages/web/src/components/share/part.tsx | 31 +------------- 3 files changed, 56 insertions(+), 30 deletions(-) create mode 100644 packages/web/src/components/share/part-utils.test.ts create mode 100644 packages/web/src/components/share/part-utils.ts diff --git a/packages/web/src/components/share/part-utils.test.ts b/packages/web/src/components/share/part-utils.test.ts new file mode 100644 index 00000000..d02c5a7e --- /dev/null +++ b/packages/web/src/components/share/part-utils.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from "bun:test" +import { flattenToolArgs } from "./part-utils" + +describe("flattenToolArgs", () => { + test("flattens nested objects and arrays (docstring example)", () => { + expect(flattenToolArgs({ a: { b: { c: 1 } }, d: [{ e: 2 }, 3] })).toEqual([ + ["a.b.c", 1], + ["d[0].e", 2], + ["d[1]", 3], + ]) + }) + + test("keeps primitives and null as leaf values", () => { + expect(flattenToolArgs({ x: 1, y: "hello", z: null, w: true })).toEqual([ + ["x", 1], + ["y", "hello"], + ["z", null], + ["w", true], + ]) + }) + + test("returns an empty array for non-object input", () => { + expect(flattenToolArgs(42)).toEqual([]) + expect(flattenToolArgs("str")).toEqual([]) + expect(flattenToolArgs(null)).toEqual([]) + expect(flattenToolArgs(undefined)).toEqual([]) + }) + + test("handles empty objects and empty arrays", () => { + expect(flattenToolArgs({})).toEqual([]) + expect(flattenToolArgs({ items: [] })).toEqual([]) + }) + + test("indexes array elements with bracket paths", () => { + expect(flattenToolArgs({ tags: ["a", "b"] })).toEqual([ + ["tags[0]", "a"], + ["tags[1]", "b"], + ]) + }) +}) diff --git a/packages/web/src/components/share/part-utils.ts b/packages/web/src/components/share/part-utils.ts new file mode 100644 index 00000000..b1fa8549 --- /dev/null +++ b/packages/web/src/components/share/part-utils.ts @@ -0,0 +1,15 @@ +// Converts nested objects/arrays into [path, value] pairs. +// E.g. {a:{b:{c:1}}, d:[{e:2}, 3]} => [["a.b.c",1], ["d[0].e",2], ["d[1]",3]] +export function flattenToolArgs(obj: unknown, prefix: string = ""): Array<[string, unknown]> { + if (typeof obj !== "object" || obj === null) return [] + + // Recurse into containers; emit a [path, value] pair for leaf (non-object) values. + const walk = (path: string, value: unknown): Array<[string, unknown]> => + value !== null && typeof value === "object" ? flattenToolArgs(value, path) : [[path, value]] + + return Object.entries(obj as Record).flatMap(([key, value]) => { + const path = prefix ? `${prefix}.${key}` : key + if (Array.isArray(value)) return value.flatMap((item, index) => walk(`${path}[${index}]`, item)) + return walk(path, value) + }) +} diff --git a/packages/web/src/components/share/part.tsx b/packages/web/src/components/share/part.tsx index 67099196..2a6d67ae 100644 --- a/packages/web/src/components/share/part.tsx +++ b/packages/web/src/components/share/part.tsx @@ -1,4 +1,5 @@ import map from "lang-map" +import { flattenToolArgs } from "./part-utils" import { DateTime } from "luxon" import { For, Show, Match, Switch, type JSX, createMemo, createSignal, type ParentProps } from "solid-js" import { @@ -753,36 +754,6 @@ export function FallbackTool(props: ToolProps) { ) } -// Converts nested objects/arrays into [path, value] pairs. -// E.g. {a:{b:{c:1}}, d:[{e:2}, 3]} => [["a.b.c",1], ["d[0].e",2], ["d[1]",3]] -function flattenToolArgs(obj: unknown, prefix: string = ""): Array<[string, unknown]> { - const entries: Array<[string, unknown]> = [] - if (typeof obj !== "object" || obj === null) return entries - - for (const [key, value] of Object.entries(obj as Record)) { - const path = prefix ? `${prefix}.${key}` : key - - if (value !== null && typeof value === "object") { - if (Array.isArray(value)) { - value.forEach((item, index) => { - const arrayPath = `${path}[${index}]` - if (item !== null && typeof item === "object") { - entries.push(...flattenToolArgs(item, arrayPath)) - } else { - entries.push([arrayPath, item]) - } - }) - } else { - entries.push(...flattenToolArgs(value, path)) - } - } else { - entries.push([path, value]) - } - } - - return entries -} - function getProvider(model: string) { const lowerModel = model.toLowerCase()