History

Enhance Tool Explorer and OpenAPI Handling

- Update styling logic in the ToolRow and GroupNode components to differentiate selected and source tools, improving visual clarity. - Modify OpenAPI preparation to always include parameter schemas, ensuring consistent schema handling. - Add tests to validate input and output hints for OpenAPI tools, ensuring accurate type representation. - Update OpenAPI cache versioning to reflect recent changes and improvements in tool discovery. - Refactor workspace tool inventory logic to streamline state management and improve performance.

Rhys Sullivan committed 8 months ago

Commit e8d1e821 parent 8ae75e18 files · +97 −11Browse files

Unified · Split

8 files
  1. executor/apps/web/src/components/tools/explorer-groups.tsx +3 −1
  2. executor/apps/web/src/components/tools/explorer-rows.tsx +4 −2
  3. executor/packages/core/src/openapi-prepare.ts +1 −1
  4. executor/packages/core/src/openapi-typing.test.ts +2 −0
  5. executor/packages/core/src/openapi/real-specs.test.ts +30 −0
  6. executor/packages/core/src/tool-discovery.test.ts +55 −0
  7. executor/packages/database/src/runtime/tool_source_loading.ts +1 −1
  8. executor/packages/database/src/runtime/workspace_tools.ts +1 −6
executor/apps/web/src/components/tools/explorer-groups.tsx+3−1
@@ -80,7 +80,9 @@ export function GroupNode({ · 79 unmodified lines
8080 "sticky bg-background/95 backdrop-blur-sm",
8181 isExpanded && "border-b border-border/30",
8282 isGroupSelected
83− ? "bg-primary/10 ring-1 ring-primary/20"
83+ ? isSource
84+ ? "bg-primary/10 ring-1 ring-primary/20"
85+ : "bg-accent/20 ring-1 ring-accent/30"
8486 : "hover:bg-accent/30",
8587 )}
8688 style={{
executor/apps/web/src/components/tools/explorer-rows.tsx+4−2
@@ -50,9 +50,11 @@ const ToolRow = memo(function ToolRow({ · 49 unmodified lines
5050 className={cn(
5151 "flex items-center gap-2 px-2 py-1.5 transition-colors cursor-pointer group/tool",
5252 expanded
53− ? "sticky bg-background/95 backdrop-blur-sm bg-accent/30"
53+ ? selected
54+ ? "sticky bg-accent ring-1 ring-accent/30"
55+ : "sticky bg-card"
5456 : selected
55− ? "bg-primary/5 ring-1 ring-primary/10"
57+ ? "bg-accent ring-1 ring-accent/30"
5658 : "hover:bg-accent/20",
5759 )}
5860 style={{
executor/packages/core/src/openapi-prepare.ts+1−1
@@ -233,7 +233,7 @@ export async function prepareOpenApiSpec( · 232 unmodified lines
233233 {
234234 includeSchemas: profile === "full",
235235 includeTypeHints: true,
236− includeParameterSchemas: profile === "full",
236+ includeParameterSchemas: true,
237237 resolveSchemaRefs: profile === "full",
238238 },
239239 );
executor/packages/core/src/openapi-typing.test.ts+2−0
@@ -100,6 +100,8 @@ describe("OpenAPI schema-first typing", () => { · 99 unmodified lines
100100 expect(getTool!.typing?.outputSchema).toBeDefined();
101101 expect(getTool!.typing?.requiredInputKeys ?? []).toContain("id");
102102 expect(getTool!.typing?.previewInputKeys ?? []).toContain("include");
103+ expect(getTool!.typing?.inputHint).toContain("id: string");
104+ expect(getTool!.typing?.inputHint).toContain("include?:");
103105 expect(getTool!.typing?.typedRef).toBeDefined();
104106 });
105107
executor/packages/core/src/openapi/real-specs.test.ts+30−0
@@ -301,4 +301,34 @@ describe("real-world OpenAPI specs", () => { · 300 unmodified lines
301301 },
302302 300_000,
303303 );
304+
305+ test(
306+ "openai: assistants cancel run keeps path parameter types in inventory mode",
307+ async () => {
308+ const openAiUrl = "https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml";
309+ const prepared = await prepareOpenApiSpec(openAiUrl, "openai", {
310+ includeDts: false,
311+ profile: "inventory",
312+ });
313+
314+ const tools = buildOpenApiToolsFromPrepared(
315+ {
316+ type: "openapi",
317+ name: "openai",
318+ spec: openAiUrl,
319+ baseUrl: prepared.servers[0] || "https://api.openai.com",
320+ },
321+ prepared,
322+ );
323+
324+ const tool = tools.find((t) => t.path === "openai.assistants.cancel_run");
325+ expect(tool).toBeDefined();
326+
327+ const inputHint = tool!.typing?.inputHint ?? "";
328+ expect(inputHint).toContain("thread_id: string");
329+ expect(inputHint).toContain("run_id: string");
330+ expect(inputHint.includes("unknown")).toBe(false);
331+ },
332+ 300_000,
333+ );
304334 });
executor/packages/core/src/tool-discovery.test.ts+55−0
@@ -412,6 +412,61 @@ test("discover depth 0 keeps full signatures", async () => { · 411 unmodified lines
412412 );
413413 });
414414
415+test("discover prefers schema over lossy typed hints", async () => {
416+ const tool = createDiscoverTool([
417+ {
418+ path: "openai.batch.create_batch",
419+ description: "Create batch",
420+ approval: "required",
421+ source: "openapi:openai",
422+ typing: {
423+ inputHint: "{ input_file_id: ...; endpoint: ... }",
424+ outputHint: "{ id: string; errors?: { message?:... } }",
425+ inputSchema: {
426+ type: "object",
427+ properties: {
428+ input_file_id: { type: "string" },
429+ endpoint: { type: "string" },
430+ completion_window: { type: "string" },
431+ },
432+ required: ["input_file_id", "endpoint", "completion_window"],
433+ },
434+ outputSchema: {
435+ type: "object",
436+ properties: {
437+ id: { type: "string" },
438+ errors: {
439+ type: "object",
440+ properties: {
441+ message: { type: "string" },
442+ },
443+ },
444+ },
445+ required: ["id"],
446+ },
447+ },
448+ run: async () => ({ id: "b_123" }),
449+ } satisfies ToolDefinition,
450+ ]);
451+
452+ const result = await tool.run(
453+ { query: "create batch", depth: 2 },
454+ { taskId: "t", workspaceId: TEST_WORKSPACE_ID, isToolAllowed: () => true },
455+ ) as {
456+ results: Array<{
457+ signatureInfo: {
458+ input: string;
459+ output: string;
460+ };
461+ }>;
462+ };
463+
464+ expect(result.results[0]?.signatureInfo.input).toContain("input_file_id: string");
465+ expect(result.results[0]?.signatureInfo.input.includes("...")).toBe(false);
466+ expect(result.results[0]?.signatureInfo.output).toContain("message?: string");
467+ expect(result.results[0]?.signatureInfo.output.includes("...")).toBe(false);
468+});
469+
415470 test("discover returns null bestPath when there are no matches", async () => {
416471 const tool = createDiscoverTool([
417472 {
executor/packages/database/src/runtime/tool_source_loading.ts+1−1
@@ -30,7 +30,7 @@ import { · 29 unmodified lines
3030
3131 const OPENAPI_SPEC_CACHE_TTL_MS = 5 * 60 * 60_000;
3232
33−const OPENAPI_PREPARED_CACHE_VERSION = "openapi_v5";
33+const OPENAPI_PREPARED_CACHE_VERSION = "openapi_v6";
3434
3535 const openApiAuthModeSchema = z.enum(["static", "account", "workspace", "organization"]);
3636
executor/packages/database/src/runtime/workspace_tools.ts+1−6
@@ -1504,11 +1504,6 @@ export async function rebuildWorkspaceToolInventoryForContext( · 1503 unmodified lines
15041504 sources.filter((source) => source.enabled),
15051505 );
15061506
1507− const alreadyReady = Boolean(
1508− state?.readyBuildId
1509− && !state?.buildingBuildId
1510− && state?.signature === registrySignature,
1511− );
15121507 const alreadyBuildingTarget = Boolean(
15131508 state?.buildingBuildId
15141509 && state?.buildingSignature === registrySignature,
@@ -1523,7 +1518,7 @@ export async function rebuildWorkspaceToolInventoryForContext( · 8 unmodified lines
15231518 && Date.now() - buildingStartedAt > REGISTRY_BUILD_STALE_MS,
15241519 );
15251520
1526− if (alreadyReady || (alreadyBuildingTarget && !staleBuildingTarget)) {
1521+ if (alreadyBuildingTarget && !staleBuildingTarget) {
15271522 return { rebuilt: false };
15281523 }
15291524