Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
931 changes: 834 additions & 97 deletions workspaces/arazzo/Explanation.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,38 @@ func (of *OperationFinder) FindByID(operationID string) *OperationInfo {
return nil
}

// parseQualifiedOperationID parses the Arazzo spec form
// "$sourceDescriptions.NAME.operationId" into (sourceName, operationId, true).
// Returns ("", "", false) for any other string.
func parseQualifiedOperationID(expr string) (string, string, bool) {
const prefix = "$sourceDescriptions."
if !strings.HasPrefix(expr, prefix) {
return "", "", false
}
rest := expr[len(prefix):]
dot := strings.Index(rest, ".")
if dot < 0 {
return "", "", false
}
return rest[:dot], rest[dot+1:], true
}

// FindByIDInSource finds an operation by its operationId within a single named
// source description. Used when the step specifies a qualified operationId like
// "$sourceDescriptions.petStoreDescription.loginUser".
// It reuses FindByID by constructing a single-entry finder scoped to that source.
func (of *OperationFinder) FindByIDInSource(sourceName, operationID string) *OperationInfo {
sourceDescRaw, ok := of.SourceDescriptions[sourceName]
if !ok {
log.Printf("Source description %q not found", sourceName)
return nil
}
scoped := &OperationFinder{
SourceDescriptions: map[string]interface{}{sourceName: sourceDescRaw},
}
return scoped.FindByID(operationID)
}

// FindByHTTPPathAndMethod finds an operation by its HTTP path and method.
func (of *OperationFinder) FindByHTTPPathAndMethod(httpPath, httpMethod string) *OperationInfo {
targetMethod := strings.ToLower(httpMethod)
Expand Down Expand Up @@ -166,7 +198,12 @@ func (of *OperationFinder) FindByHTTPPathAndMethod(httpPath, httpMethod string)

// FindByPath finds an operation by source URL and JSON pointer.
// operationPath format: sourceURL#jsonPointer
// sourceURL may be a bare name, a URL, or a braced expression like
// "{$sourceDescriptions.petStoreDescription.url}".
func (of *OperationFinder) FindByPath(sourceURL, jsonPointer string) *OperationInfo {
// Strip surrounding braces and resolve "$sourceDescriptions.NAME.url" to "NAME"
sourceURL = resolveSourceDescriptionRef(strings.Trim(sourceURL, "{}"))

// Find the source description
sourceName, sourceDesc := of.findSourceDescription(sourceURL)
if sourceDesc == nil {
Expand All @@ -177,6 +214,18 @@ func (of *OperationFinder) FindByPath(sourceURL, jsonPointer string) *OperationI
return of.parseOperationPointer(jsonPointer, sourceName, sourceDesc)
}

// resolveSourceDescriptionRef converts a "$sourceDescriptions.NAME.url" expression
// (already stripped of surrounding braces) to just "NAME", which directly matches
// the key in the SourceDescriptions map. Any other string is returned unchanged.
func resolveSourceDescriptionRef(expr string) string {
const prefix = "$sourceDescriptions."
const suffix = ".url"
if strings.HasPrefix(expr, prefix) && strings.HasSuffix(expr, suffix) {
return expr[len(prefix) : len(expr)-len(suffix)]
}
return expr
}

// findSourceDescription finds a source description by URL or name.
func (of *OperationFinder) findSourceDescription(sourceURL string) (string, map[string]interface{}) {
// Exact name match
Expand Down Expand Up @@ -219,6 +268,13 @@ func (of *OperationFinder) parseOperationPointer(jsonPointer, sourceName string,
return info
}

// Approach 4: Path-only pointer (no HTTP method) — picks the first available method.
// Handles e.g. /paths/~1pet~1findByStatus without a trailing /get.
info = of.resolvePathOnly(jsonPointer, sourceName, sourceDesc)
if info != nil {
return info
}

log.Printf("Could not parse operation pointer: %s", jsonPointer)
return nil
}
Expand Down Expand Up @@ -427,6 +483,62 @@ func (of *OperationFinder) handleSpecialCases(jsonPointer, sourceName string, so
return nil
}

// resolvePathOnly handles JSON pointers that reference a path item rather than a
// specific operation, e.g. /paths/~1pet~1findByStatus (no HTTP method suffix).
// It returns the first HTTP method found for the decoded path, in httpMethods order.
func (of *OperationFinder) resolvePathOnly(jsonPointer, sourceName string, sourceDesc map[string]interface{}) *OperationInfo {
Comment on lines +486 to +489
if !strings.HasPrefix(jsonPointer, "/paths/") {
return nil
}

// Everything after "/paths/" is the single encoded path token (e.g. ~1pet~1findByStatus).
encodedPath := strings.TrimPrefix(jsonPointer, "/paths/")
if encodedPath == "" {
return nil
}

// Decode JSON Pointer encoding: ~1 → /, ~0 → ~
httpPath := strings.ReplaceAll(encodedPath, "~1", "/")
httpPath = strings.ReplaceAll(httpPath, "~0", "~")
if !strings.HasPrefix(httpPath, "/") {
httpPath = "/" + httpPath
}

paths := toMap(sourceDesc["paths"])
if paths == nil {
return nil
}

pathItem := toMap(paths[httpPath])
if pathItem == nil {
return nil
}

baseURL, err := getBaseURL(sourceDesc)
if err != nil {
return nil
}

// Return the first HTTP method found for this path
for _, method := range httpMethods {
operation := toMap(pathItem[method])
if operation == nil {
continue
}
opID, _ := operation["operationId"].(string)
return &OperationInfo{
Source: sourceName,
Path: httpPath,
Method: method,
URL: baseURL + httpPath,
Operation: operation,
OperationID: opID,
}
}

return nil
}

// GetOperationsForWorkflow finds all operation references in a workflow dict.
func (of *OperationFinder) GetOperationsForWorkflow(workflow map[string]interface{}) []*OperationInfo {
var operations []*OperationInfo
Expand All @@ -439,7 +551,13 @@ func (of *OperationFinder) GetOperationsForWorkflow(workflow map[string]interfac
}

if opID, ok := step["operationId"].(string); ok && opID != "" {
if info := of.FindByID(opID); info != nil {
var info *OperationInfo
if sourceName, bareID, ok := parseQualifiedOperationID(opID); ok {
info = of.FindByIDInSource(sourceName, bareID)
} else {
info = of.FindByID(opID)
}
if info != nil {
operations = append(operations, info)
}
} else if opPath, ok := step["operationPath"].(string); ok && opPath != "" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,21 +280,25 @@ func (se *StepExecutor) ExecuteStep(step map[string]interface{}, workflow map[st

// findOperation locates the API operation for a step.
func (se *StepExecutor) findOperation(step map[string]interface{}) *OperationInfo {
// Try operationId first
// Try operationId first.
// Supports both plain operationId and the qualified Arazzo spec form
// "$sourceDescriptions.NAME.operationId" which scopes the search to one source.
Comment on lines +284 to +285
if opID, ok := step["operationId"].(string); ok && opID != "" {
log.Printf("Looking up operation by ID: %s", opID)
if sourceName, bareID, ok := parseQualifiedOperationID(opID); ok {
return se.OperationFinder.FindByIDInSource(sourceName, bareID)
}
return se.OperationFinder.FindByID(opID)
}

// Try operationPath (e.g. "{$sourceDescriptions.petstore.url}#/pets/{petId}")
// Try operationPath (e.g. "{$sourceDescriptions.petstore.url}#/paths/~1pets/get")
if opPath, ok := step["operationPath"].(string); ok && opPath != "" {
log.Printf("Looking up operation by path: %s", opPath)
// Parse the operationPath: "{sourceURL}#{jsonPointer}" or "sourceURL#jsonPointer"
// FindByPath handles brace-stripping and $sourceDescriptions expression resolution.
parts := strings.SplitN(opPath, "#", 2)
if len(parts) == 2 {
sourceURL := strings.Trim(parts[0], "{}")
jsonPointer := parts[1]
return se.OperationFinder.FindByPath(sourceURL, jsonPointer)
return se.OperationFinder.FindByPath(parts[0], parts[1])
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
arazzo: 1.0.0
info:
title: Toolshop OperationPath Example
summary: Demonstrates using operationPath with expressions and path-only pointers.
description: This example shows how to use the Arazzo spec compliant operationPath syntax.
version: 1.0.0

sourceDescriptions:
- name: toolshopDescription
url: toolshop-openapi.yaml
type: openapi

workflows:
- workflowId: operationPathWorkflow
summary: Demonstrate operationPath support
description: This workflow retrieves categories and a specific category using operationPath.
steps:
- stepId: getCategoriesStep
description: Retrieve all categories using operationPath with an expression and no method
# Uses expression resolution and defaults to 'get' method since it's the only one available at /categories
operationPath: '{$sourceDescriptions.toolshopDescription.url}#/paths/~1categories'
successCriteria:
- condition: $statusCode == 200
outputs:
firstCategoryId: "$response.body#/0/id"

- stepId: getSpecificCategoryStep
description: Retrieve a specific category using a parameterized path item pointer
# The pointer MUST match the path string in toolshop-openapi.yaml exactly: /categories/tree/{categoryId}
# / is escaped as ~1
operationPath: '{$sourceDescriptions.toolshopDescription.url}#/paths/~1categories~1tree~1{categoryId}'
parameters:
- name: categoryId
in: path
value: $steps.getCategoriesStep.outputs.firstCategoryId
successCriteria:
- condition: $statusCode == 200

outputs:
categoryData: $steps.getSpecificCategoryStep.outputs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
arazzo: 1.0.0
info:
title: Toolshop Qualified OperationID Example
summary: Demonstrates using scoped operationId references.
description: This example shows how to use the Arazzo spec compliant "$sourceDescriptions.NAME.operationId" syntax.
version: 1.0.0

sourceDescriptions:
- name: toolshopDescription
url: toolshop-openapi.yaml
type: openapi

workflows:
- workflowId: qualifiedOpIdWorkflow
summary: Demonstrate qualified operationId support
description: This workflow retrieves products using a qualified operationId scoped to the toolshop source.
steps:
- stepId: getProductsStep
description: Retrieve products using a scoped operationId
# Uses the qualified form to ensure we pick getProducts from toolshopDescription
operationId: $sourceDescriptions.toolshopDescription.getProducts
parameters:
- name: page
in: query
value: 1
successCriteria:
- condition: $statusCode == 200
outputs:
firstProductId: "$response.body#/data/0/id"

- stepId: getProductDetailStep
description: Retrieve details using another scoped operationId
operationId: $sourceDescriptions.toolshopDescription.getProduct
parameters:
- name: productId
in: path
value: $steps.getProductsStep.outputs.firstProductId
successCriteria:
- condition: $statusCode == 200

outputs:
productName: $steps.getProductDetailStep.outputs.response.body.name
57 changes: 57 additions & 0 deletions workspaces/arazzo/handover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Project Handover: Arazzo Visualizer & Go Runner Engine
1. Project Overview
The project is a VS Code Extension for the Arazzo Specification (an OpenAPI Initiative standard for multi-step API workflows). The extension provides interactive diagrams, AI-assisted workflow generation via GitHub Copilot, and a bundled execution environment. The core focus of this session was expanding the bundled Go Runner to support deterministic, headless execution alongside its existing AI/MCP capabilities.

2. Technical Architecture & Stack
Frontend/UI: VS Code Webviews (React, TypeScript). Uses the Visitor Design Pattern to parse Arazzo ASTs and build interactive workflow graphs.

Backend/Execution: A custom, bundled Go Runner Engine cross-compiled for Windows, Mac, and Linux.

Server Structure: A single Go HTTP server (ServeMux) routing traffic to different handlers based on the client (AI vs. Human).

Observability: OpenTelemetry (OTel) integration. The Go Runner streams OTLP spans to an internal VS Code Trace Server. The Webview uses RPC layers to map these spans to the graph, creating Live Path Highlighting during execution.

3. Recent Implementation: The Direct REST Endpoint (/run)
We successfully designed the addition of a direct HTTP endpoint to allow developers to execute workflows via curl or Postman, bypassing the AI Copilot layer.

Endpoint Design (REST Standard): POST /run/{workflowId}

Payload: Accepts a JSON body containing an inputs map.

Validation (The "Bouncer"): Implemented strict Required + Types fail-fast validation. The engine checks the incoming request against the Arazzo YAML schema. If an input is missing or the wrong type, it blocks the execution and returns an HTTP 400 Bad Request.

Execution Response: If the workflow runs but fails at an API step (e.g., target API is down), the server correctly returns an HTTP 200 OK with a JSON payload specifying "status": "failed" and the exact error message.

4. UI, VS Code Integration, and Quality of Life Updates
We finalized the release notes for the latest extension update, which includes:

"Try with curl": A CodeLens and UI button to execute workflows in the terminal while animating the diagram in real-time.

Advanced Copilot Control: Copilot can now start the server, execute workflows, and toggle security settings.

Smart TLS Recovery: The Go engine auto-detects "unknown authority" certificate errors and offers a one-click bypass in the UI.

Input Configuration Panel: A dedicated UI panel for managing workflow inputs with strict inline validation.

Code Quality: Enforced a "Single Source of Truth" formatting rule in the TypeScript codebase (using stringifyInputValue instead of hardcoded strings for boolean defaults).

Windows Execution Quirks: Identified and resolved issues with PowerShell stripping JSON quotes during curl.exe commands, recommending Invoke-RestMethod as the native Windows alternative.

5. Marketing & Documentation
README: Refined the VS Code Marketplace README to heavily emphasize the live execution dashboard, OpenTelemetry tracing, the bundled Go runner, and the dual nature of "Try with AI" vs "Try with curl" (including the headless REST API).

Launch Strategy: Outlined a developer-focused marketing plan including architecture deep-dive blogs (Dev.to/Hashnode), a "Show HN" Hacker News launch, and short-form GIF/video content for social media.

6. University Internship Report Context
We generated a massive, 35-40 page structured LaTeX document for an academic industrial training report (Color Code: CS Orange). The report comprehensively covers the intern's contributions to WSO2, detailing:

The LSP, RPC layers, and Webview architecture.

The Visitor pattern for AST parsing.

OpenTelemetry integration and the dual Arazzo/Trace server setup.

Soft skills development, organizational SWOT analysis, and references to the Arazzo and MCP specifications.

7. Future Horizons: Arazzo for MCP
We discussed a cutting-edge theoretical architecture: adapting the Arazzo Go Runner to orchestrate Model Context Protocol (MCP) tools instead of standard REST APIs. This would involve creating a custom mcp-stdio source type in the YAML, allowing developers to build strictly deterministic, non-hallucinating agentic workflows.
Comment on lines +40 to +57
33 changes: 33 additions & 0 deletions workspaces/arazzo/readme(project).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Stuff to know
- in this repo the arazzo-extension-OTeL-v2 is the correct branch to be working on. as of now it is upto date with the wso2 arazzo-extension branch (I did a git pull origin arazzo-extension). this means it has the latest arazzo visulaizer plugin updates
- other than the plugin code it also has the explanation.md fully updated and pushed
- as for the arazzo-extension-cliupdate-v3 branch don't use it. it just has the --docker flag and the -o flag added to the cli and the pllan.md files in addition to this branch which are not usefull for the plugin. those are some of the personal updates done (needed them for the CLI tool in the arazzo-mcp-generator)
- so in the future make sure to use this v2 branch and continue
Comment on lines +1 to +5

## Starting
- need to have go(0.26 something) and node(i used 25) installed
- update the rush.json to be compatible with the node version
- do rush install (later if needed rush update. this can be useful if rush build is failing)
- the rush build -t arazzo-visualizer
- both need to be run from where the rush.json is at
Comment on lines +8 to +12
- then in the run and debug select the arazzo-visualizer and run
- then go to workspaces/arazzo/arazzo-designer-visualizer and type npm run start to start the UI
- to build the CLI go to arazzo-designer-cli and type .\build-binaries.ps1
- this will build the binaries and copy it to the arazzo-desigenr-cli/cli(not neeed but good for the code structure) and to arazzo-designer-extension/ci(needed)
- Later on as for next steps see of the arazzo-mcp-generator repo has complete binaries. if so they can be directly taken and put in the arazzo-designer-extension/cli (this is not done yet and when this is done the arazzo-designer-cli will no longer be needed)

## Future work
- Complete the arazzo-mcp-generator repo and replace the binaries in the plugin with those
- add askills.md file to teach copilot to create arazzo files

PS: when working on this project i had the wso2/arazzo-extension as origin and HimethW/(mybranch) as myfork remotes

PS: to connect a mcp with vscode create .vscode/mcp.json and
{
"servers": {
"arazzo": {
"type": "http",
"url": "http://localhost:9900/mcp"
}
}
}
Loading