chore: add docs url for integration nodes

This commit is contained in:
Abhishek Kumar 2026-07-15 18:54:07 +05:30
parent c650ccc5dd
commit a422241517
13 changed files with 95 additions and 13 deletions

View file

@ -25,6 +25,7 @@ from api.services.workflow.node_specs.model_spec import (
"Paygent is a post-call usage-tracking and billing integration. "
"It does not participate in the conversation graph and should not be connected to other nodes."
),
docs_url="https://docs.dograh.com/integrations/paygent",
category=NodeCategory.integration,
icon="CreditCard",
examples=[

View file

@ -36,6 +36,7 @@ _COST_RATE_RENDERER_OPTIONS = PropertyRendererOptions(
"Tuner is a post-call observability export. It does not participate in the "
"conversation graph and should not be connected to other nodes."
),
docs_url="https://docs.dograh.com/integrations/tuner",
category=NodeCategory.integration,
icon="Activity",
examples=[

View file

@ -241,9 +241,7 @@ class DograhUltravoxRealtimeLLMService(UltravoxRealtimeLLMService):
"call(s) after bot turn ended"
)
for tool_name, invocation_id, parameters in invocations:
await super()._handle_tool_invocation(
tool_name, invocation_id, parameters
)
await super()._handle_tool_invocation(tool_name, invocation_id, parameters)
async def _send_tool_result(self, tool_call_id: str, result: str):
is_node_transition = tool_call_id in self._pending_node_transition_tool_call_ids

View file

@ -300,6 +300,10 @@ class NodeSpec(BaseModel):
default=None,
description="LLM-only guidance; omitted from the UI.",
)
docs_url: Optional[str] = Field(
default=None,
description="Documentation URL shown in the node editor.",
)
category: NodeCategory
icon: str # lucide-react icon name (e.g., "Play")
version: str = "1.0.0"
@ -312,8 +316,8 @@ class NodeSpec(BaseModel):
def to_mcp_dict(self) -> dict[str, Any]:
"""Lean projection of this spec for the `get_node_type` MCP tool.
Drops node-level UI metadata (`display_name`, `category`, `icon`,
`version`) and the per-property rendering concerns trimmed by
Drops node-level UI metadata (`display_name`, `docs_url`, `category`,
`icon`, `version`) and the per-property rendering concerns trimmed by
`PropertySpec.to_mcp_dict`, leaving just the authoring-relevant
schema the LLM consumes when composing a workflow. The full spec is
still served verbatim to the frontend renderer (REST `node-types`

View file

@ -33,6 +33,7 @@ class NodeSpecMetadata:
category: NodeCategory
icon: str
llm_hint: str | None = None
docs_url: str | None = None
version: str = "1.0.0"
examples: tuple[NodeExample, ...] = ()
graph_constraints: GraphConstraints | None = None
@ -91,6 +92,7 @@ def node_spec(
category: NodeCategory,
icon: str,
llm_hint: str | None = None,
docs_url: str | None = None,
version: str = "1.0.0",
examples: list[NodeExample] | tuple[NodeExample, ...] = (),
graph_constraints: GraphConstraints | None = None,
@ -104,6 +106,7 @@ def node_spec(
category=category,
icon=icon,
llm_hint=llm_hint,
docs_url=docs_url,
version=version,
examples=tuple(examples),
graph_constraints=graph_constraints,
@ -137,6 +140,7 @@ def build_spec(model_cls: type[BaseModel]) -> NodeSpec:
display_name=metadata.display_name,
description=metadata.description,
llm_hint=metadata.llm_hint,
docs_url=metadata.docs_url,
category=metadata.category,
icon=metadata.icon,
version=metadata.version,

View file

@ -336,6 +336,18 @@ def test_tuner_cost_rate_fields_use_typed_renderer_options():
)
@pytest.mark.parametrize(
("spec_name", "expected_docs_url"),
[
("paygent", "https://docs.dograh.com/integrations/paygent"),
("tuner", "https://docs.dograh.com/integrations/tuner"),
],
)
def test_integration_node_docs_url(spec_name: str, expected_docs_url: str):
spec = next(spec for spec in all_specs() if spec.name == spec_name)
assert spec.docs_url == expected_docs_url
def test_property_renderer_options_reject_unknown_hints():
with pytest.raises(ValidationError):
PropertyRendererOptions.model_validate({"layout": {"width": "half"}})
@ -352,6 +364,7 @@ def test_property_renderer_options_reject_unknown_hints():
_UI_ONLY_KEYS = frozenset(
{
"display_name",
"docs_url",
"icon",
"category",
"version",

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: dograh-openapi-XXXXXX.json.uTKtHKJw6v
# timestamp: 2026-07-15T11:25:06+00:00
# filename: dograh-openapi-XXXXXX.json.di0tn7Gw6b
# timestamp: 2026-07-15T13:20:43+00:00
from __future__ import annotations
@ -947,6 +947,10 @@ class NodeSpec(BaseModel):
"""
LLM-only guidance; omitted from the UI.
"""
docs_url: Annotated[str | None, Field(title='Docs Url')] = None
"""
Documentation URL shown in the node editor.
"""
category: NodeCategory
icon: Annotated[str, Field(title='Icon')]
version: Annotated[str | None, Field(title='Version')] = '1.0.0'

View file

@ -809,6 +809,11 @@ export interface components {
* @description LLM-only guidance; omitted from the UI.
*/
llm_hint?: string | null;
/**
* Docs Url
* @description Documentation URL shown in the node editor.
*/
docs_url?: string | null;
category: components["schemas"]["NodeCategory"];
/** Icon */
icon: string;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -401,7 +401,7 @@ export type AzureRealtimeLlmConfiguration = {
/**
* Api Version
*
* Azure OpenAI API version.
* Azure OpenAI Realtime protocol version. Use 'v1' for the GA API; date-based versions select the deprecated preview endpoint.
*/
api_version?: string;
};
@ -3814,6 +3814,12 @@ export type NodeSpec = {
* LLM-only guidance; omitted from the UI.
*/
llm_hint?: string | null;
/**
* Docs Url
*
* Documentation URL shown in the node editor.
*/
docs_url?: string | null;
category: NodeCategory;
/**
* Icon
@ -7479,6 +7485,38 @@ export type CompleteTransferFunctionCallApiV1TelephonyTransferResultTransferIdPo
200: unknown;
};
export type HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostData = {
body?: never;
path: {
/**
* Transfer Id
*/
transfer_id: string;
};
query?: never;
url: '/api/v1/telephony/cloudonix/transfer-result/{transfer_id}';
};
export type HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostErrors = {
/**
* Not found
*/
404: unknown;
/**
* Validation Error
*/
422: HttpValidationError;
};
export type HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostError = HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostErrors[keyof HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostErrors];
export type HandleCloudonixTransferResultApiV1TelephonyCloudonixTransferResultTransferIdPostResponses = {
/**
* Successful Response
*/
200: unknown;
};
export type HandleCloudonixStatusCallbackApiV1TelephonyCloudonixStatusCallbackWorkflowRunIdPostData = {
body?: never;
path: {

View file

@ -597,7 +597,7 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
: { source: true, target: true });
const badge = getBadgeForSpec(spec, styleVariant);
const Icon = spec ? resolveIcon(spec.icon) : Circle;
const docUrl = DOC_URL_BY_SPEC[type];
const docUrl = spec?.docs_url ?? DOC_URL_BY_SPEC[type];
const contentLabel = spec?.properties.some((p) => p.name === "prompt")
? "Prompt"
: "Details";