Skip to content

feat(studio): OpenAPI Converter for TS Capabilities#492

Open
steramae-nvidia wants to merge 1 commit into
mainfrom
steramae/sdk-capabilities
Open

feat(studio): OpenAPI Converter for TS Capabilities#492
steramae-nvidia wants to merge 1 commit into
mainfrom
steramae/sdk-capabilities

Conversation

@steramae-nvidia

@steramae-nvidia steramae-nvidia commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

This PR adds a new script for generating capabilities and some gateway tool definitions (search, describe, read and run) to help with consuming said capabilities.

The idea here, is an agent/model will inveitably want to interact with nemo platform through the API. There already exists nemo skills that describe CLI commands which work well but only for CLI based AIs. In Studio, a web UI based app, we would want an agent to be able to interact with the platform similar to how a user would through publicly available web based APIs. This isn't meant as a replacement for the CLI skills but rather a new tool for web based interaction of agents and nemo platform. Since this is auto generated from the openapi yaml, it is the clearest connection from what is available on the API.

example capability

{
    "name": "access_secret_apis_secrets_v2_workspaces__workspace__secrets__name__access_get",
    "service": "platform",
    "method": "GET",
    "path": "/apis/secrets/v2/workspaces/{workspace}/secrets/{name}/access",
    "summary": "Access Secret",
    "description": "Access the value of a secret.",
    "tags": [
      "Secrets"
    ],
    "readOnly": true,
    "pathParams": [
      "name",
      "workspace"
    ],
    "queryParams": [],
    "hasBody": false,
    "bodyRequired": false,
    "inputSchema": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "title": "Name"
        },
        "workspace": {
          "type": "string",
          "title": "Workspace"
        }
      },
      "required": [
        "name",
        "workspace"
      ]
    }
  }

Summary by CodeRabbit

  • New Features

    • Added a new capabilities system for discovering, describing, and running API operations.
    • Introduced support for exporting tools in OpenAI, Anthropic, and MCP-compatible formats.
    • Added a ready-to-use gateway and bundled capability registry for SDK consumers.
    • Added generation support so capability metadata stays in sync with API specs.
  • Bug Fixes

    • Improved cache invalidation so capability updates regenerate correctly.
  • Tests

    • Added coverage for capability search, invocation, gateway behavior, and tool adapters.

Signed-off-by: Sean Teramae <steramae@nvidia.com>
@steramae-nvidia steramae-nvidia requested review from a team as code owners June 26, 2026 21:57
@github-actions github-actions Bot added the feat label Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SDK adds capability types, lookup and invocation helpers, gateway tools, provider adapters, generated registry output, build-script wiring, and Vitest coverage for invocation, search, gateway, and adapters.

Changes

SDK capability pipeline

Layer / File(s) Summary
Model and lookup
web/packages/sdk/src/capabilities/types.ts, web/packages/sdk/src/capabilities/registry.ts, web/packages/sdk/src/capabilities/invoke.ts, web/packages/sdk/src/capabilities/fetchers.ts
Adds capability metadata types, summary/search helpers, request execution, and default service fetchers.
Gateway and entrypoint
web/packages/sdk/src/capabilities/gateway.ts, web/packages/sdk/src/capabilities/index.ts
Adds the search/describe/read/run gateway and the public capability entrypoint.
Tool adapters
web/packages/sdk/src/capabilities/adapters/*
Adds Anthropic, OpenAI, and MCP tool conversions.
Registry generation and wiring
web/packages/sdk/orval/generate-capabilities.ts, web/packages/sdk/generateAll.ts, web/packages/sdk/package.json
Adds the capability registry generator, cache invalidation input, and the new npm script.
Tests and Vitest setup
web/packages/sdk/src/capabilities/capabilities.test.ts, web/packages/sdk/tsconfig.json, web/packages/sdk/vitest.config.ts
Adds Vitest setup and coverage for invocation, search, gateway, and adapter behavior.

Suggested reviewers

  • dmariali
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main change: generating TypeScript capabilities from OpenAPI specs.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch steramae/sdk-capabilities

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/packages/sdk/src/capabilities/adapters/anthropic.ts`:
- Line 4: The import in anthropic.ts is using a relative path that violates the
SDK TypeScript import rule. Replace the `Capability` and `JsonSchema` import
from `../types` with the configured SDK alias path used elsewhere in the
package. Update the top-level import only, keeping the rest of the `anthropic`
adapter logic unchanged.

In `@web/packages/sdk/src/capabilities/adapters/openai.ts`:
- Line 4: The import in the openai adapter uses a relative path that violates
the SDK import rule. Update the import in the openai.ts module to use the
configured SDK absolute alias instead of ../types, keeping the same Capability
and JsonSchema symbols. Make sure all references in this file continue to
resolve through the tsconfig path-mapped alias and no relative TypeScript
imports remain here.

In `@web/packages/sdk/src/capabilities/capabilities.test.ts`:
- Around line 4-10: The test file is using relative imports that violate the
repository’s alias-import rule and will fail linting. Update the imports in
capabilities.test.ts to use the configured absolute alias paths instead of
./adapters/*, ./gateway, ./invoke, ./registry, and ./types, keeping the same
referenced symbols such as toAnthropicTools, callMcpTool, toMcpTools,
toOpenAITools, createGateway, invokeCapability, searchCapabilities, and the type
imports.

In `@web/packages/sdk/src/capabilities/fetchers.ts`:
- Around line 10-15: This module uses relative import paths for the generated
fetchers, which violates the web TypeScript import rule. Update the imports in
fetchers.ts to use the package alias form instead of ../../ paths, keeping the
same symbols such as customFetch as agentsFetch, dataDesignerFetch,
evaluatorFetch, platformFetch, and safeSynthesizerFetch, and ensure all
references continue to resolve through the tsconfig path mapping.

In `@web/packages/sdk/src/capabilities/gateway.ts`:
- Around line 87-91: The searchCapabilities call in gateway.ts currently
forwards args.limit directly, which allows 0 or negative values despite the
schema requiring a minimum of 1. Update the limit handling in the capability
search flow to clamp or validate args.limit as a positive integer before passing
it into searchCapabilities, and fall back to undefined for any non-integer,
zero, or negative input so the helper only receives safe limits.

In `@web/packages/sdk/src/capabilities/registry.ts`:
- Line 4: The import in registry.ts uses a relative path and violates the SDK
import rule enforced by no-relative-import-paths. Update the CapabilityMeta
import in the registry module to use the configured package alias path instead
of ./types, matching the project’s absolute import convention.
- Around line 61-78: The search logic in the capabilities registry accepts a raw
numeric limit and passes it directly to slice, so negative or invalid values can
produce unintended results. Update the search helper in registry.ts to validate
the limit before slicing, either by clamping it to a non-negative integer or by
rejecting invalid inputs. Keep the fix centered around the search_capabilities
flow that builds scored results and returns the sliced summaries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e2d68e96-e2a4-4575-a454-8a5bd7042ee3

📥 Commits

Reviewing files that changed from the base of the PR and between 470971a and c99459f.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • web/packages/sdk/generateAll.ts
  • web/packages/sdk/orval/generate-capabilities.ts
  • web/packages/sdk/package.json
  • web/packages/sdk/src/capabilities/adapters/anthropic.ts
  • web/packages/sdk/src/capabilities/adapters/mcp.ts
  • web/packages/sdk/src/capabilities/adapters/openai.ts
  • web/packages/sdk/src/capabilities/capabilities.test.ts
  • web/packages/sdk/src/capabilities/fetchers.ts
  • web/packages/sdk/src/capabilities/gateway.ts
  • web/packages/sdk/src/capabilities/index.ts
  • web/packages/sdk/src/capabilities/invoke.ts
  • web/packages/sdk/src/capabilities/registry.ts
  • web/packages/sdk/src/capabilities/types.ts
  • web/packages/sdk/tsconfig.json
  • web/packages/sdk/vitest.config.ts

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { Capability, JsonSchema } from '../types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the SDK alias instead of a relative import.

../types violates the web TypeScript import rule. Replace it with the configured absolute alias path. As per coding guidelines, "Never use relative imports. Always use absolute alias paths ... Use absolute imports via tsconfig path mapping. Never use relative imports in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/adapters/anthropic.ts` at line 4, The
import in anthropic.ts is using a relative path that violates the SDK TypeScript
import rule. Replace the `Capability` and `JsonSchema` import from `../types`
with the configured SDK alias path used elsewhere in the package. Update the
top-level import only, keeping the rest of the `anthropic` adapter logic
unchanged.

Source: Coding guidelines

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { Capability, JsonSchema } from '../types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the SDK alias instead of a relative import.

../types violates the web TypeScript import rule. Replace it with the configured absolute alias path. As per coding guidelines, "Never use relative imports. Always use absolute alias paths ... Use absolute imports via tsconfig path mapping. Never use relative imports in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/adapters/openai.ts` at line 4, The import
in the openai adapter uses a relative path that violates the SDK import rule.
Update the import in the openai.ts module to use the configured SDK absolute
alias instead of ../types, keeping the same Capability and JsonSchema symbols.
Make sure all references in this file continue to resolve through the tsconfig
path-mapped alias and no relative TypeScript imports remain here.

Source: Coding guidelines

Comment on lines +4 to +10
import { toAnthropicTools } from './adapters/anthropic';
import { callMcpTool, toMcpTools } from './adapters/mcp';
import { toOpenAITools } from './adapters/openai';
import { createGateway } from './gateway';
import { invokeCapability } from './invoke';
import { searchCapabilities } from './registry';
import type { CapabilityContext, CapabilityMeta, FetchRequest } from './types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace relative imports with configured path aliases.

Line 4-10 violates the repo rule and will trip no-relative-import-paths. Use the package’s absolute alias paths here instead of ./.... As per coding guidelines, "web/**/*.{ts,tsx,js,jsx}: Never use relative imports. Always use absolute alias paths ... ESLint enforces this with no-relative-import-paths."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/capabilities.test.ts` around lines 4 - 10,
The test file is using relative imports that violate the repository’s
alias-import rule and will fail linting. Update the imports in
capabilities.test.ts to use the configured absolute alias paths instead of
./adapters/*, ./gateway, ./invoke, ./registry, and ./types, keeping the same
referenced symbols such as toAnthropicTools, callMcpTool, toMcpTools,
toOpenAITools, createGateway, invokeCapability, searchCapabilities, and the type
imports.

Source: Coding guidelines

Comment on lines +10 to +15
import { customFetch as agentsFetch } from '../../generated/fetchers/agents';
import { customFetch as dataDesignerFetch } from '../../generated/fetchers/data-designer';
import { customFetch as evaluatorFetch } from '../../generated/fetchers/evaluator';
import { customFetch as platformFetch } from '../../generated/fetchers/platform';
import { customFetch as safeSynthesizerFetch } from '../../generated/fetchers/safe-synthesizer';
import type { Fetcher } from './types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use absolute alias imports in this module.

These relative imports violate the web TypeScript import rule and will trip no-relative-import-paths. Switch them to the package alias form. As per coding guidelines, "Never use relative imports. Always use absolute alias paths ... Use absolute imports via tsconfig path mapping. Never use relative imports in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/fetchers.ts` around lines 10 - 15, This
module uses relative import paths for the generated fetchers, which violates the
web TypeScript import rule. Update the imports in fetchers.ts to use the package
alias form instead of ../../ paths, keeping the same symbols such as customFetch
as agentsFetch, dataDesignerFetch, evaluatorFetch, platformFetch, and
safeSynthesizerFetch, and ensure all references continue to resolve through the
tsconfig path mapping.

Source: Coding guidelines

Comment on lines +87 to +91
const results = searchCapabilities(capabilities, query, {
limit: typeof args.limit === 'number' ? args.limit : undefined,
service: asString(args.service),
readOnly: typeof args.readOnly === 'boolean' ? args.readOnly : undefined,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp limit to a positive integer.

Schema says minimum: 1, but runtime accepts any number. A model passing 0 or a negative value yields empty or surprising slice(0, negative) results.

Proposed guard
-        limit: typeof args.limit === 'number' ? args.limit : undefined,
+        limit:
+          typeof args.limit === 'number' && args.limit >= 1
+            ? Math.floor(args.limit)
+            : undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const results = searchCapabilities(capabilities, query, {
limit: typeof args.limit === 'number' ? args.limit : undefined,
service: asString(args.service),
readOnly: typeof args.readOnly === 'boolean' ? args.readOnly : undefined,
});
const results = searchCapabilities(capabilities, query, {
limit:
typeof args.limit === 'number' && args.limit >= 1
? Math.floor(args.limit)
: undefined,
service: asString(args.service),
readOnly: typeof args.readOnly === 'boolean' ? args.readOnly : undefined,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/gateway.ts` around lines 87 - 91, The
searchCapabilities call in gateway.ts currently forwards args.limit directly,
which allows 0 or negative values despite the schema requiring a minimum of 1.
Update the limit handling in the capability search flow to clamp or validate
args.limit as a positive integer before passing it into searchCapabilities, and
fall back to undefined for any non-integer, zero, or negative input so the
helper only receives safe limits.

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { CapabilityMeta } from './types';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace the relative import with the package alias.

./types violates the SDK import rule and will trip no-relative-import-paths. Use the configured absolute alias instead. As per coding guidelines, "Never use relative imports. Always use absolute alias paths ... Use absolute imports via tsconfig path mapping. Never use relative imports in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/registry.ts` at line 4, The import in
registry.ts uses a relative path and violates the SDK import rule enforced by
no-relative-import-paths. Update the CapabilityMeta import in the registry
module to use the configured package alias path instead of ./types, matching the
project’s absolute import convention.

Source: Coding guidelines

Comment on lines +61 to +78
const { limit = 25, service, readOnly } = options;
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);

const scored: Array<{ meta: CapabilityMeta; score: number }> = [];
for (const meta of capabilities) {
if (service && meta.service !== service) continue;
if (readOnly !== undefined && meta.readOnly !== readOnly) continue;

const hay = haystack(meta);
if (!terms.every((t) => hay.includes(t))) continue;

const nameOrPath = `${meta.name} ${meta.path}`.toLowerCase();
const score = terms.reduce((acc, t) => acc + (nameOrPath.includes(t) ? 1 : 0), 0);
scored.push({ meta, score });
}

scored.sort((a, b) => b.score - a.score || a.meta.name.localeCompare(b.meta.name));
return scored.slice(0, limit).map(({ meta }) => toSummary(meta));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate limit before slicing.

search_capabilities forwards raw numeric limit here, so limit: -1 returns all-but-last because slice(0, -1) is valid JS. Clamp to a non-negative integer or reject invalid values.

Suggested fix
-  const { limit = 25, service, readOnly } = options;
+  const { service, readOnly } = options;
+  const limit =
+    options.limit === undefined ? 25 : Math.max(0, Math.trunc(options.limit));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { limit = 25, service, readOnly } = options;
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
const scored: Array<{ meta: CapabilityMeta; score: number }> = [];
for (const meta of capabilities) {
if (service && meta.service !== service) continue;
if (readOnly !== undefined && meta.readOnly !== readOnly) continue;
const hay = haystack(meta);
if (!terms.every((t) => hay.includes(t))) continue;
const nameOrPath = `${meta.name} ${meta.path}`.toLowerCase();
const score = terms.reduce((acc, t) => acc + (nameOrPath.includes(t) ? 1 : 0), 0);
scored.push({ meta, score });
}
scored.sort((a, b) => b.score - a.score || a.meta.name.localeCompare(b.meta.name));
return scored.slice(0, limit).map(({ meta }) => toSummary(meta));
const { service, readOnly } = options;
const limit =
options.limit === undefined ? 25 : Math.max(0, Math.trunc(options.limit));
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
const scored: Array<{ meta: CapabilityMeta; score: number }> = [];
for (const meta of capabilities) {
if (service && meta.service !== service) continue;
if (readOnly !== undefined && meta.readOnly !== readOnly) continue;
const hay = haystack(meta);
if (!terms.every((t) => hay.includes(t))) continue;
const nameOrPath = `${meta.name} ${meta.path}`.toLowerCase();
const score = terms.reduce((acc, t) => acc + (nameOrPath.includes(t) ? 1 : 0), 0);
scored.push({ meta, score });
}
scored.sort((a, b) => b.score - a.score || a.meta.name.localeCompare(b.meta.name));
return scored.slice(0, limit).map(({ meta }) => toSummary(meta));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/sdk/src/capabilities/registry.ts` around lines 61 - 78, The
search logic in the capabilities registry accepts a raw numeric limit and passes
it directly to slice, so negative or invalid values can produce unintended
results. Update the search helper in registry.ts to validate the limit before
slicing, either by clamping it to a non-negative integer or by rejecting invalid
inputs. Keep the fix centered around the search_capabilities flow that builds
scored results and returns the sliced summaries.

@github-actions

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 21330/27927 76.4% 61.4%
Integration Tests 12358/26696 46.3% 19.8%

@steramae-nvidia steramae-nvidia changed the title feat(studio): SDK capabilities feat(studio): OpenAPI Converter for TS Capabilities Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant