diff --git a/README.md b/README.md index 3bd74b8d1f..2898bcd0e1 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ You can install ApiDOM packages using [npm CLI](https://docs.npmjs.com/cli): $ npm install @swagger-api/apidom-json-pointer-relative $ npm install @swagger-api/apidom-logging $ npm install @swagger-api/apidom-ls + $ npm install @swagger-api/apidom-ns-a2a-1 $ npm install @swagger-api/apidom-ns-api-design-systems $ npm install @swagger-api/apidom-ns-arazzo-1 $ npm install @swagger-api/apidom-ns-asyncapi-2 @@ -93,6 +94,8 @@ You can install ApiDOM packages using [npm CLI](https://docs.npmjs.com/cli): $ npm install @swagger-api/apidom-ns-openapi-3-0 $ npm install @swagger-api/apidom-ns-openapi-3-1 $ npm install @swagger-api/apidom-parser + $ npm install @swagger-api/apidom-parser-adapter-a2a-json-1 + $ npm install @swagger-api/apidom-parser-adapter-a2a-yaml-1 $ npm install @swagger-api/apidom-parser-adapter-api-design-systems-json $ npm install @swagger-api/apidom-parser-adapter-api-design-systems-yaml $ npm install @swagger-api/apidom-parser-adapter-arazzo-json-1 @@ -628,6 +631,7 @@ The namespace packages provide ApiDOM namespaces for specifications, consisiting Available namespaces: +- [A2A 1.0.0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-a2a-1) - [API Design Systems](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-api-design-systems) - [Arazzo 1.0.1](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-arazzo-1) - [AsyncAPI 2.x.y](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-asyncapi-2) @@ -648,6 +652,7 @@ The base parser adapters support [JSON](https://github.com/swagger-api/apidom/tr Available parser adapters for ApiDOM namespaces: +- A2A 1.0.0 - [JSON](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-a2a-json-1) / [YAML](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-a2a-yaml-1) - API Design Systems - [JSON](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-api-design-systems-json) / [YAML](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-api-design-systems-yaml) - Arazzo 1.0.1 - [JSON](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-arazzo-json-1) / [YAML](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-arazzo-yaml-1) - AsyncAPI 2.x.y - [JSON](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-asyncapi-json-2) / [YAML](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-asyncapi-yaml-2) @@ -671,6 +676,7 @@ Available parse strategies: - YAML 1.2 format - Binary format - ApiDOM - JSON +- A2A 1.0.0 - JSON / YAML - API Design Systems - JSON / YAML - Arazzo 1.0.1 - JSON / YAML - AsyncAPI 2.x.y - JSON / YAML diff --git a/package-lock.json b/package-lock.json index 3c32512445..9e06040dfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6518,6 +6518,10 @@ "resolved": "packages/apidom-ls", "link": true }, + "node_modules/@swagger-api/apidom-ns-a2a-1": { + "resolved": "packages/apidom-ns-a2a-1", + "link": true + }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { "resolved": "packages/apidom-ns-api-design-systems", "link": true @@ -6574,6 +6578,14 @@ "resolved": "packages/apidom-parser", "link": true }, + "node_modules/@swagger-api/apidom-parser-adapter-a2a-json-1": { + "resolved": "packages/apidom-parser-adapter-a2a-json-1", + "link": true + }, + "node_modules/@swagger-api/apidom-parser-adapter-a2a-yaml-1": { + "resolved": "packages/apidom-parser-adapter-a2a-yaml-1", + "link": true + }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { "resolved": "packages/apidom-parser-adapter-api-design-systems-json", "link": true @@ -24462,6 +24474,7 @@ "@swagger-api/apidom-core": "^1.11.2", "@swagger-api/apidom-json-path": "^1.11.2", "@swagger-api/apidom-json-pointer": "^1.11.2", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", "@swagger-api/apidom-ns-api-design-systems": "^1.11.2", "@swagger-api/apidom-ns-asyncapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-2": "^1.11.2", @@ -24469,6 +24482,8 @@ "@swagger-api/apidom-ns-openapi-3-1": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-2": "^1.11.2", "@swagger-api/apidom-parser": "^1.11.2", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.11.1", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.11.2", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.11.2", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.11.2", @@ -24519,6 +24534,19 @@ } } }, + "packages/apidom-ns-a2a-1": { + "name": "@swagger-api/apidom-ns-a2a-1", + "version": "1.11.1", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, "packages/apidom-ns-api-design-systems": { "name": "@swagger-api/apidom-ns-api-design-systems", "version": "1.11.2", @@ -24728,6 +24756,34 @@ "ramda-adjunct": "^5.0.0" } }, + "packages/apidom-parser-adapter-a2a-json-1": { + "name": "@swagger-api/apidom-parser-adapter-a2a-json-1", + "version": "1.11.1", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-json": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "packages/apidom-parser-adapter-a2a-yaml-1": { + "name": "@swagger-api/apidom-parser-adapter-a2a-yaml-1", + "version": "1.11.1", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, "packages/apidom-parser-adapter-api-design-systems-json": { "name": "@swagger-api/apidom-parser-adapter-api-design-systems-json", "version": "1.11.2", @@ -25153,12 +25209,15 @@ }, "devDependencies": { "@swagger-api/apidom-json-pointer": "*", + "@swagger-api/apidom-ns-a2a-1": "*", "@swagger-api/apidom-ns-arazzo-1": "*", "@swagger-api/apidom-ns-asyncapi-2": "*", "@swagger-api/apidom-ns-openapi-2": "*", "@swagger-api/apidom-ns-openapi-3-0": "*", "@swagger-api/apidom-ns-openapi-3-1": "*", "@swagger-api/apidom-ns-openapi-3-2": "*", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "*", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "*", "@swagger-api/apidom-parser-adapter-arazzo-json-1": "*", @@ -25181,12 +25240,15 @@ }, "optionalDependencies": { "@swagger-api/apidom-json-pointer": "^1.11.2", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", "@swagger-api/apidom-ns-arazzo-1": "^1.11.2", "@swagger-api/apidom-ns-asyncapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-0": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-1": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-2": "^1.11.2", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.11.1", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.11.2", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.11.2", "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.11.2", diff --git a/packages/apidom-ls/package.json b/packages/apidom-ls/package.json index 1b5d191f10..e9529c5f71 100644 --- a/packages/apidom-ls/package.json +++ b/packages/apidom-ls/package.json @@ -101,6 +101,7 @@ "@swagger-api/apidom-core": "^1.11.2", "@swagger-api/apidom-json-path": "^1.11.2", "@swagger-api/apidom-json-pointer": "^1.11.2", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", "@swagger-api/apidom-ns-api-design-systems": "^1.11.2", "@swagger-api/apidom-ns-asyncapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-2": "^1.11.2", @@ -108,6 +109,8 @@ "@swagger-api/apidom-ns-openapi-3-1": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-2": "^1.11.2", "@swagger-api/apidom-parser": "^1.11.2", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.11.1", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.11.2", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.11.2", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.11.2", diff --git a/packages/apidom-ls/src/config/a2a/a2a1/completion.ts b/packages/apidom-ls/src/config/a2a/a2a1/completion.ts new file mode 100644 index 0000000000..cc5e0804e5 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/completion.ts @@ -0,0 +1,346 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'name', + insertText: 'name', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[Agent Card name](https://a2a-protocol.org/latest/definitions/#agent-card)\n\\\n\\\nA human-readable name for the agent. Example: `"Recipe Agent"`.', + }, + targetSpecs: A2A1, + }, + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'A human-readable description of the agent, helping users and other agents understand its purpose.', + }, + targetSpecs: A2A1, + }, + { + label: 'version', + insertText: 'version', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'The agent\'s deployment version. Example: `"1.0.0"`.', + }, + targetSpecs: A2A1, + }, + { + label: 'iconUrl', + insertText: 'iconUrl', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Optional URL to an icon for the agent.', + }, + targetSpecs: A2A1, + }, + { + label: 'documentationUrl', + insertText: 'documentationUrl', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'A URL providing additional documentation about the agent.', + }, + targetSpecs: A2A1, + }, + { + label: 'provider', + insertText: 'provider', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[Agent Provider Object](https://a2a-protocol.org/latest/definitions/#agent-provider)\n\\\n\\\nThe service provider of the agent (organization, URL).', + }, + targetSpecs: A2A1, + }, + { + label: 'capabilities', + insertText: 'capabilities', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[Agent Capabilities Object](https://a2a-protocol.org/latest/definitions/#agent-capabilities)\n\\\n\\\nOptional capabilities supported by the agent (streaming, push notifications, extensions, extended agent card).', + }, + targetSpecs: A2A1, + }, + { + label: 'defaultInputModes', + insertText: 'defaultInputModes', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'Media types the agent accepts as input across all skills. May be overridden per skill.', + }, + targetSpecs: A2A1, + }, + { + label: 'defaultOutputModes', + insertText: 'defaultOutputModes', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Media types the agent can produce as output across all skills.', + }, + targetSpecs: A2A1, + }, + { + label: 'supportedInterfaces', + insertText: 'supportedInterfaces', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'Ordered list of supported interfaces (URL + protocol binding + protocol version). The first entry is preferred.', + }, + targetSpecs: A2A1, + }, + { + label: 'skills', + insertText: 'skills', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + '[Agent Skill Objects](https://a2a-protocol.org/latest/definitions/#agent-skill)\n\\\n\\\nDistinct capabilities the agent can perform.', + }, + targetSpecs: A2A1, + }, + { + label: 'securitySchemes', + insertText: 'securitySchemes', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'A map of named security schemes (`apiKeySecurityScheme`, `httpAuthSecurityScheme`, `oauth2SecurityScheme`, `openIdConnectSecurityScheme`, `mtlsSecurityScheme`).', + }, + targetSpecs: A2A1, + }, + { + label: 'securityRequirements', + insertText: 'securityRequirements', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Security requirements that clients must satisfy to contact the agent.', + }, + targetSpecs: A2A1, + }, + { + label: 'signatures', + insertText: 'signatures', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'JSON Web Signatures computed for this Agent Card (RFC 7515 JWS).', + }, + targetSpecs: A2A1, + }, + // Nested: AgentCapabilities properties + { + label: 'streaming', + insertText: 'streaming', + kind: 14, + format: CompletionFormat.UNQUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Indicates if the agent supports streaming responses.', + }, + conditions: [{ function: 'isInsideAgentCapabilities' }], + targetSpecs: A2A1, + }, + { + label: 'pushNotifications', + insertText: 'pushNotifications', + kind: 14, + format: CompletionFormat.UNQUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'Indicates if the agent supports sending push notifications for asynchronous task updates.', + }, + conditions: [{ function: 'isInsideAgentCapabilities' }], + targetSpecs: A2A1, + }, + // Nested: AgentSkill properties + { + label: 'id', + insertText: 'id', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Unique identifier for the skill.', + }, + conditions: [{ function: 'isInsideAgentSkill' }], + targetSpecs: A2A1, + }, + { + label: 'tags', + insertText: 'tags', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: "Keywords describing the skill's capabilities.", + }, + conditions: [{ function: 'isInsideAgentSkill' }], + targetSpecs: A2A1, + }, + { + label: 'examples', + insertText: 'examples', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Example prompts or scenarios this skill can handle.', + }, + conditions: [{ function: 'isInsideAgentSkill' }], + targetSpecs: A2A1, + }, + // Nested: SecurityScheme subtype fields + { + label: 'apiKeySecurityScheme', + insertText: 'apiKeySecurityScheme', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'API key authentication scheme. Set this OR one of the other security scheme subfields.', + }, + conditions: [{ function: 'isInsideSecurityScheme' }], + targetSpecs: A2A1, + }, + { + label: 'httpAuthSecurityScheme', + insertText: 'httpAuthSecurityScheme', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'HTTP authentication scheme (Basic, Bearer, etc., per RFC 7235).', + }, + conditions: [{ function: 'isInsideSecurityScheme' }], + targetSpecs: A2A1, + }, + { + label: 'oauth2SecurityScheme', + insertText: 'oauth2SecurityScheme', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'OAuth 2.0 authentication scheme.', + }, + conditions: [{ function: 'isInsideSecurityScheme' }], + targetSpecs: A2A1, + }, + { + label: 'openIdConnectSecurityScheme', + insertText: 'openIdConnectSecurityScheme', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'OpenID Connect authentication scheme.', + }, + conditions: [{ function: 'isInsideSecurityScheme' }], + targetSpecs: A2A1, + }, + { + label: 'mtlsSecurityScheme', + insertText: 'mtlsSecurityScheme', + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Mutual TLS authentication scheme.', + }, + conditions: [{ function: 'isInsideSecurityScheme' }], + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/documentation.ts b/packages/apidom-ls/src/config/a2a/a2a1/documentation.ts new file mode 100644 index 0000000000..64f34a9950 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/documentation.ts @@ -0,0 +1,47 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentCard fields. Only fields whose type + * isn't obvious from the value (objects, arrays of typed elements, maps) get + * entries — primitive string/boolean fields are self-documenting via the + * spec's element types. + */ +const documentation = [ + { + target: 'provider', + docs: '[Agent Provider Object](https://a2a-protocol.org/latest/definitions/#agent-provider) — the service provider of the agent (organization name and URL).', + targetSpecs: A2A1, + }, + { + target: 'capabilities', + docs: '[Agent Capabilities Object](https://a2a-protocol.org/latest/definitions/#agent-capabilities) — required. Declares the A2A capability set supported by the agent (streaming, push notifications, extensions, extended agent card).', + targetSpecs: A2A1, + }, + { + target: 'supportedInterfaces', + docs: 'Ordered list of [Agent Interface Objects](https://a2a-protocol.org/latest/definitions/#agent-interface). Each entry combines a URL, protocol binding (`JSONRPC`, `GRPC`, `HTTP+JSON`), and the A2A protocol version exposed at that endpoint. The first entry is preferred.', + targetSpecs: A2A1, + }, + { + target: 'skills', + docs: 'Array of [Agent Skill Objects](https://a2a-protocol.org/latest/definitions/#agent-skill) representing distinct capabilities the agent can perform.', + targetSpecs: A2A1, + }, + { + target: 'securitySchemes', + docs: 'Map of named [Security Scheme Objects](https://a2a-protocol.org/latest/definitions/#security-scheme). Each value is a wrapper with one of: `apiKeySecurityScheme`, `httpAuthSecurityScheme`, `oauth2SecurityScheme`, `openIdConnectSecurityScheme`, or `mtlsSecurityScheme`.', + targetSpecs: A2A1, + }, + { + target: 'securityRequirements', + docs: 'Array of [Security Requirement Objects](https://a2a-protocol.org/latest/definitions/#security-requirement) describing which security schemes clients must satisfy to contact the agent.', + targetSpecs: A2A1, + }, + { + target: 'signatures', + docs: 'Array of [Agent Card Signature Objects](https://a2a-protocol.org/latest/definitions/#agent-card-signature) — JSON Web Signatures (JWS, RFC 7515) computed for this Agent Card.', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/allowed-fields.ts new file mode 100644 index 0000000000..54d6c8577c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/allowed-fields.ts @@ -0,0 +1,35 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [ + [ + 'name', + 'description', + 'version', + 'iconUrl', + 'documentationUrl', + 'provider', + 'capabilities', + 'defaultInputModes', + 'defaultOutputModes', + 'supportedInterfaces', + 'skills', + 'securitySchemes', + 'securityRequirements', + 'signatures', + ], + ], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--required.ts new file mode 100644 index 0000000000..3284024935 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const capabilitiesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_CAPABILITIES_REQUIRED, + source: 'apilint', + message: "should always have a 'capabilities' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['capabilities'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'capabilities' field", + action: 'addChild', + snippetYaml: 'capabilities:\n \n', + snippetJson: '"capabilities": {},\n', + }, + ], + }, +}; + +export default capabilitiesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--type.ts new file mode 100644 index 0000000000..d4b6fb7f6c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/capabilities--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_CAPABILITIES_TYPE, + source: 'apilint', + message: "'capabilities' must be an Agent Capabilities Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['agentCapabilities'], + marker: 'value', + target: 'capabilities', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--required.ts new file mode 100644 index 0000000000..ba4de7d4ad --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const defaultInputModesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DEFAULT_INPUT_MODES_REQUIRED, + source: 'apilint', + message: "should always have a 'defaultInputModes' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['defaultInputModes'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'defaultInputModes' field", + action: 'addChild', + snippetYaml: 'defaultInputModes:\n - \n', + snippetJson: '"defaultInputModes": [],\n', + }, + ], + }, +}; + +export default defaultInputModesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--type.ts new file mode 100644 index 0000000000..03a1e5b068 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-input-modes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DEFAULT_INPUT_MODES_TYPE, + source: 'apilint', + message: "'defaultInputModes' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'defaultInputModes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--required.ts new file mode 100644 index 0000000000..9bf85fb67f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const defaultOutputModesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DEFAULT_OUTPUT_MODES_REQUIRED, + source: 'apilint', + message: "should always have a 'defaultOutputModes' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['defaultOutputModes'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'defaultOutputModes' field", + action: 'addChild', + snippetYaml: 'defaultOutputModes:\n - \n', + snippetJson: '"defaultOutputModes": [],\n', + }, + ], + }, +}; + +export default defaultOutputModesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--type.ts new file mode 100644 index 0000000000..3a5d3f13ce --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/default-output-modes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DEFAULT_OUTPUT_MODES_TYPE, + source: 'apilint', + message: "'defaultOutputModes' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'defaultOutputModes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/description--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/description--required.ts new file mode 100644 index 0000000000..98b9a18b72 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/description--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DESCRIPTION_REQUIRED, + source: 'apilint', + message: "should always have a 'description' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['description'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'description' field", + action: 'addChild', + snippetYaml: "description: ''\n", + snippetJson: '"description": "",\n', + }, + ], + }, +}; + +export default descriptionRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/description--type.ts new file mode 100644 index 0000000000..ab89f3515c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/documentation-url--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/documentation-url--type.ts new file mode 100644 index 0000000000..5779550543 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/documentation-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_DOCUMENTATION_URL_TYPE, + source: 'apilint', + message: "'documentationUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'documentationUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/icon-url--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/icon-url--type.ts new file mode 100644 index 0000000000..9611915059 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/icon-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_ICON_URL_TYPE, + source: 'apilint', + message: "'iconUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'iconUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/index.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/index.ts new file mode 100644 index 0000000000..e368a88698 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/index.ts @@ -0,0 +1,51 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import nameRequiredLint from './name--required.ts'; +import descriptionRequiredLint from './description--required.ts'; +import supportedInterfacesRequiredLint from './supported-interfaces--required.ts'; +import versionRequiredLint from './version--required.ts'; +import capabilitiesRequiredLint from './capabilities--required.ts'; +import defaultInputModesRequiredLint from './default-input-modes--required.ts'; +import defaultOutputModesRequiredLint from './default-output-modes--required.ts'; +import skillsRequiredLint from './skills--required.ts'; +import nameTypeLint from './name--type.ts'; +import descriptionTypeLint from './description--type.ts'; +import versionTypeLint from './version--type.ts'; +import iconUrlTypeLint from './icon-url--type.ts'; +import documentationUrlTypeLint from './documentation-url--type.ts'; +import providerTypeLint from './provider--type.ts'; +import capabilitiesTypeLint from './capabilities--type.ts'; +import defaultInputModesTypeLint from './default-input-modes--type.ts'; +import defaultOutputModesTypeLint from './default-output-modes--type.ts'; +import supportedInterfacesTypeLint from './supported-interfaces--type.ts'; +import skillsTypeLint from './skills--type.ts'; +import securitySchemesTypeLint from './security-schemes--type.ts'; +import securityRequirementsTypeLint from './security-requirements--type.ts'; +import signaturesTypeLint from './signatures--type.ts'; + +const lints = [ + allowedFieldsLint, + nameRequiredLint, + descriptionRequiredLint, + supportedInterfacesRequiredLint, + versionRequiredLint, + capabilitiesRequiredLint, + defaultInputModesRequiredLint, + defaultOutputModesRequiredLint, + skillsRequiredLint, + nameTypeLint, + descriptionTypeLint, + versionTypeLint, + iconUrlTypeLint, + documentationUrlTypeLint, + providerTypeLint, + capabilitiesTypeLint, + defaultInputModesTypeLint, + defaultOutputModesTypeLint, + supportedInterfacesTypeLint, + skillsTypeLint, + securitySchemesTypeLint, + securityRequirementsTypeLint, + signaturesTypeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/name--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/name--required.ts new file mode 100644 index 0000000000..3c5fd679ea --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/name--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const nameRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_NAME_REQUIRED, + source: 'apilint', + message: "should always have a 'name' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['name'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'name' field", + action: 'addChild', + snippetYaml: "name: ''\n", + snippetJson: '"name": "",\n', + }, + ], + }, +}; + +export default nameRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/name--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/name--type.ts new file mode 100644 index 0000000000..873c569f4c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/name--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_NAME_TYPE, + source: 'apilint', + message: "'name' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'name', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/provider--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/provider--type.ts new file mode 100644 index 0000000000..a36338a6ad --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/provider--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_PROVIDER_TYPE, + source: 'apilint', + message: "'provider' must be an Agent Provider Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['agentProvider'], + marker: 'value', + target: 'provider', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/security-requirements--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/security-requirements--type.ts new file mode 100644 index 0000000000..59c4452287 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/security-requirements--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SECURITY_REQUIREMENTS_TYPE, + source: 'apilint', + message: "'securityRequirements' must be an array of Security Requirement Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['securityRequirement']], + marker: 'value', + target: 'securityRequirements', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/security-schemes--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/security-schemes--type.ts new file mode 100644 index 0000000000..10d325c58a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/security-schemes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SECURITY_SCHEMES_TYPE, + source: 'apilint', + message: "'securitySchemes' must be a map of Security Scheme Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintChildrenOfElementsOrClasses', + linterParams: [['securityScheme']], + marker: 'value', + target: 'securitySchemes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/signatures--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/signatures--type.ts new file mode 100644 index 0000000000..f5f8ca885e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/signatures--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SIGNATURES_TYPE, + source: 'apilint', + message: "'signatures' must be an array of Agent Card Signature Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['agentCardSignature']], + marker: 'value', + target: 'signatures', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--required.ts new file mode 100644 index 0000000000..89cf9605cd --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const skillsRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SKILLS_REQUIRED, + source: 'apilint', + message: "should always have a 'skills' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['skills'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'skills' field", + action: 'addChild', + snippetYaml: 'skills:\n - \n', + snippetJson: '"skills": [],\n', + }, + ], + }, +}; + +export default skillsRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--type.ts new file mode 100644 index 0000000000..8b2e6e7b44 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/skills--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SKILLS_TYPE, + source: 'apilint', + message: "'skills' must be an array of Agent Skill Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['agentSkill']], + marker: 'value', + target: 'skills', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--required.ts new file mode 100644 index 0000000000..583e1d2881 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const supportedInterfacesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SUPPORTED_INTERFACES_REQUIRED, + source: 'apilint', + message: "should always have a 'supportedInterfaces' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['supportedInterfaces'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'supportedInterfaces' field", + action: 'addChild', + snippetYaml: 'supportedInterfaces:\n - \n', + snippetJson: '"supportedInterfaces": [],\n', + }, + ], + }, +}; + +export default supportedInterfacesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--type.ts new file mode 100644 index 0000000000..9a5f8ec77e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/supported-interfaces--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_SUPPORTED_INTERFACES_TYPE, + source: 'apilint', + message: "'supportedInterfaces' must be an array of Agent Interface Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['agentInterface']], + marker: 'value', + target: 'supportedInterfaces', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/version--required.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/version--required.ts new file mode 100644 index 0000000000..c4bbd41d31 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/version--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const versionRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_VERSION_REQUIRED, + source: 'apilint', + message: "should always have a 'version' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['version'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'version' field", + action: 'addChild', + snippetYaml: "version: ''\n", + snippetJson: '"version": "",\n', + }, + ], + }, +}; + +export default versionRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/lint/version--type.ts b/packages/apidom-ls/src/config/a2a/a2a1/lint/version--type.ts new file mode 100644 index 0000000000..77ee2c7485 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/lint/version--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_FIELD_VERSION_TYPE, + source: 'apilint', + message: "'version' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'version', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/a2a1/meta.ts b/packages/apidom-ls/src/config/a2a/a2a1/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/a2a1/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/completion.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/completion.ts new file mode 100644 index 0000000000..e3ea93a14b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/completion.ts @@ -0,0 +1,64 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'streaming', + insertText: 'streaming', + kind: 14, + format: CompletionFormat.UNQUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Indicates if the agent supports streaming responses.', + }, + targetSpecs: A2A1, + }, + { + label: 'pushNotifications', + insertText: 'pushNotifications', + kind: 14, + format: CompletionFormat.UNQUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'Indicates if the agent supports sending push notifications for asynchronous task updates.', + }, + targetSpecs: A2A1, + }, + { + label: 'extendedAgentCard', + insertText: 'extendedAgentCard', + kind: 14, + format: CompletionFormat.UNQUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Indicates if the agent supports providing an extended agent card when authenticated.', + }, + targetSpecs: A2A1, + }, + { + label: 'extensions', + insertText: 'extensions', + kind: 14, + format: CompletionFormat.ARRAY, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'A list of protocol extensions supported by the agent.', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/documentation.ts new file mode 100644 index 0000000000..fb3ed9d563 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/documentation.ts @@ -0,0 +1,30 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentCapabilities fields. + * See [AgentCapabilities](https://a2a-protocol.org/latest/specification/#443-agentcapabilities). + */ +const documentation = [ + { + target: 'streaming', + docs: 'Indicates if the agent supports streaming responses (boolean).', + targetSpecs: A2A1, + }, + { + target: 'pushNotifications', + docs: 'Indicates if the agent supports sending push notifications for asynchronous task updates (boolean).', + targetSpecs: A2A1, + }, + { + target: 'extensions', + docs: 'Array of [Agent Extension Objects](https://a2a-protocol.org/latest/specification/#444-agentextension) — protocol extensions supported by the agent.', + targetSpecs: A2A1, + }, + { + target: 'extendedAgentCard', + docs: 'Indicates if the agent supports providing an extended agent card when authenticated (boolean).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/allowed-fields.ts new file mode 100644 index 0000000000..90cf32bf16 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['streaming', 'pushNotifications', 'extendedAgentCard', 'extensions']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extended-agent-card--type.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extended-agent-card--type.ts new file mode 100644 index 0000000000..38d96ca1ee --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extended-agent-card--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CAPABILITIES_FIELD_EXTENDED_AGENT_CARD_TYPE, + source: 'apilint', + message: "'extendedAgentCard' must be a boolean", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['boolean'], + marker: 'value', + target: 'extendedAgentCard', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extensions--type.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extensions--type.ts new file mode 100644 index 0000000000..0fa7b0185b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/extensions--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CAPABILITIES_FIELD_EXTENSIONS_TYPE, + source: 'apilint', + message: "'extensions' must be an array of Agent Extension Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['agentExtension']], + marker: 'value', + target: 'extensions', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/index.ts new file mode 100644 index 0000000000..e95ed9732d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/index.ts @@ -0,0 +1,15 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import streamingLint from './streaming--type.ts'; +import pushNotificationsLint from './push-notifications--type.ts'; +import extendedAgentCardLint from './extended-agent-card--type.ts'; +import extensionsLint from './extensions--type.ts'; + +const lints = [ + allowedFieldsLint, + streamingLint, + pushNotificationsLint, + extendedAgentCardLint, + extensionsLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/push-notifications--type.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/push-notifications--type.ts new file mode 100644 index 0000000000..562801f1ed --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/push-notifications--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CAPABILITIES_FIELD_PUSH_NOTIFICATIONS_TYPE, + source: 'apilint', + message: "'pushNotifications' must be a boolean", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['boolean'], + marker: 'value', + target: 'pushNotifications', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/streaming--type.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/streaming--type.ts new file mode 100644 index 0000000000..114f29fc70 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/lint/streaming--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CAPABILITIES_FIELD_STREAMING_TYPE, + source: 'apilint', + message: "'streaming' must be a boolean", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['boolean'], + marker: 'value', + target: 'streaming', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-capabilities/meta.ts b/packages/apidom-ls/src/config/a2a/agent-capabilities/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-capabilities/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/completion.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/completion.ts new file mode 100644 index 0000000000..78d7468769 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/completion.ts @@ -0,0 +1,37 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const signatureField = ( + label: string, + format: CompletionFormat, + docs: string, +): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + signatureField( + 'protected', + CompletionFormat.QUOTED, + 'Base64url-encoded JWS Protected Header (JOSE).', + ), + signatureField( + 'signature', + CompletionFormat.QUOTED, + 'Base64url-encoded signature value over the AgentCard payload.', + ), + signatureField('header', CompletionFormat.OBJECT, 'Unprotected JWS header parameters (JOSE).'), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/documentation.ts new file mode 100644 index 0000000000..95e07ff6ea --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/documentation.ts @@ -0,0 +1,25 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentCardSignature fields (RFC 7515 JWS). + * See [AgentCardSignature](https://a2a-protocol.org/latest/specification/#447-agentcardsignature). + */ +const documentation = [ + { + target: 'protected', + docs: 'The protected JWS header for the signature (string, required). A base64url-encoded JSON object.', + targetSpecs: A2A1, + }, + { + target: 'signature', + docs: 'The computed signature, base64url-encoded (string, required).', + targetSpecs: A2A1, + }, + { + target: 'header', + docs: 'The unprotected JWS header values (object).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/allowed-fields.ts new file mode 100644 index 0000000000..a8733bb92b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['protected', 'signature', 'header']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/header--type.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/header--type.ts new file mode 100644 index 0000000000..16684d7de0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/header--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_SIGNATURE_FIELD_HEADER_TYPE, + source: 'apilint', + message: "'header' must be an object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['object'], + marker: 'value', + target: 'header', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/index.ts new file mode 100644 index 0000000000..efe8b613a8 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/index.ts @@ -0,0 +1,17 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import protectedRequiredLint from './protected--required.ts'; +import signatureRequiredLint from './signature--required.ts'; +import protectedLint from './protected--type.ts'; +import signatureLint from './signature--type.ts'; +import headerLint from './header--type.ts'; + +const lints = [ + allowedFieldsLint, + protectedRequiredLint, + signatureRequiredLint, + protectedLint, + signatureLint, + headerLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--required.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--required.ts new file mode 100644 index 0000000000..8eb6c41b3b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const protectedRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_SIGNATURE_FIELD_PROTECTED_REQUIRED, + source: 'apilint', + message: "should always have a 'protected' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['protected'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'protected' field", + action: 'addChild', + snippetYaml: "protected: ''\n", + snippetJson: '"protected": "",\n', + }, + ], + }, +}; + +export default protectedRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--type.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--type.ts new file mode 100644 index 0000000000..413b949005 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/protected--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_SIGNATURE_FIELD_PROTECTED_TYPE, + source: 'apilint', + message: "'protected' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'protected', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--required.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--required.ts new file mode 100644 index 0000000000..d08f6614a8 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const signatureRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_SIGNATURE_FIELD_SIGNATURE_REQUIRED, + source: 'apilint', + message: "should always have a 'signature' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['signature'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'signature' field", + action: 'addChild', + snippetYaml: "signature: ''\n", + snippetJson: '"signature": "",\n', + }, + ], + }, +}; + +export default signatureRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--type.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--type.ts new file mode 100644 index 0000000000..a2be7d397c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/lint/signature--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_CARD_SIGNATURE_FIELD_SIGNATURE_TYPE, + source: 'apilint', + message: "'signature' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'signature', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-card-signature/meta.ts b/packages/apidom-ls/src/config/a2a/agent-card-signature/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-card-signature/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/completion.ts b/packages/apidom-ls/src/config/a2a/agent-extension/completion.ts new file mode 100644 index 0000000000..a17fab1e9c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/completion.ts @@ -0,0 +1,64 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'uri', + insertText: 'uri', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'The unique URI identifying the extension (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'A human-readable description of how this agent uses the extension (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'required', + insertText: 'required', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + "If true, the client must understand and comply with the extension's requirements (boolean).", + }, + targetSpecs: A2A1, + }, + { + label: 'params', + insertText: 'params', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Optional extension-specific configuration parameters (object).', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-extension/documentation.ts new file mode 100644 index 0000000000..f74d13aed6 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/documentation.ts @@ -0,0 +1,30 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentExtension fields. + * See [AgentExtension](https://a2a-protocol.org/latest/specification/#444-agentextension). + */ +const documentation = [ + { + target: 'uri', + docs: 'The unique URI identifying the extension (string).', + targetSpecs: A2A1, + }, + { + target: 'description', + docs: 'A human-readable description of how this agent uses the extension (string).', + targetSpecs: A2A1, + }, + { + target: 'required', + docs: "If true, the client must understand and comply with the extension's requirements (boolean).", + targetSpecs: A2A1, + }, + { + target: 'params', + docs: 'Optional extension-specific configuration parameters (object).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/allowed-fields.ts new file mode 100644 index 0000000000..d2f1fb2262 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['uri', 'description', 'required', 'params']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/description--type.ts new file mode 100644 index 0000000000..8501301763 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_EXTENSION_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/index.ts new file mode 100644 index 0000000000..cf28e86343 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/index.ts @@ -0,0 +1,15 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import uriTypeLint from './uri--type.ts'; +import descriptionTypeLint from './description--type.ts'; +import requiredTypeLint from './required--type.ts'; +import paramsTypeLint from './params--type.ts'; + +const lints = [ + allowedFieldsLint, + uriTypeLint, + descriptionTypeLint, + requiredTypeLint, + paramsTypeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/params--type.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/params--type.ts new file mode 100644 index 0000000000..d34f921b35 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/params--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const paramsTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_EXTENSION_FIELD_PARAMS_TYPE, + source: 'apilint', + message: "'params' must be an object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['object'], + marker: 'value', + target: 'params', + targetSpecs: A2A1, +}; + +export default paramsTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/required--type.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/required--type.ts new file mode 100644 index 0000000000..6d1d3d0315 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/required--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const requiredTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_EXTENSION_FIELD_REQUIRED_TYPE, + source: 'apilint', + message: "'required' must be a boolean", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['boolean'], + marker: 'value', + target: 'required', + targetSpecs: A2A1, +}; + +export default requiredTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/lint/uri--type.ts b/packages/apidom-ls/src/config/a2a/agent-extension/lint/uri--type.ts new file mode 100644 index 0000000000..81843cfcbf --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/lint/uri--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const uriTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_EXTENSION_FIELD_URI_TYPE, + source: 'apilint', + message: "'uri' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'uri', + targetSpecs: A2A1, +}; + +export default uriTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-extension/meta.ts b/packages/apidom-ls/src/config/a2a/agent-extension/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-extension/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/completion.ts b/packages/apidom-ls/src/config/a2a/agent-interface/completion.ts new file mode 100644 index 0000000000..974f5bb014 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/completion.ts @@ -0,0 +1,64 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'url', + insertText: 'url', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'The URL where this interface is available. Must be a valid absolute HTTPS URL in production.', + }, + targetSpecs: A2A1, + }, + { + label: 'protocolBinding', + insertText: 'protocolBinding', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'The protocol binding (e.g. `JSONRPC`, `GRPC`, `HTTP+JSON`).', + }, + targetSpecs: A2A1, + }, + { + label: 'protocolVersion', + insertText: 'protocolVersion', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'The version of the A2A protocol this interface exposes (e.g. `"1.0"`).', + }, + targetSpecs: A2A1, + }, + { + label: 'tenant', + insertText: 'tenant', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'Optional opaque tenant identifier for multi-tenant A2A endpoints.', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-interface/documentation.ts new file mode 100644 index 0000000000..cdc0242669 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/documentation.ts @@ -0,0 +1,30 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentInterface fields. + * See [AgentInterface](https://a2a-protocol.org/latest/specification/#446-agentinterface). + */ +const documentation = [ + { + target: 'url', + docs: 'The URL where this interface is available (string, required). Must be a valid absolute HTTPS URL in production.', + targetSpecs: A2A1, + }, + { + target: 'protocolBinding', + docs: 'The protocol binding supported at this URL (string, required). Officially supported values: `JSONRPC`, `GRPC`, `HTTP+JSON`. Custom bindings SHOULD use a URI.', + targetSpecs: A2A1, + }, + { + target: 'protocolVersion', + docs: 'The version of the A2A protocol this interface exposes (string, required). Examples: `"0.3"`, `"1.0"`.', + targetSpecs: A2A1, + }, + { + target: 'tenant', + docs: 'Optional opaque routing identifier for a specific agent or tenant served behind a single A2A endpoint (string).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/allowed-fields.ts new file mode 100644 index 0000000000..4653ef954d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['url', 'protocolBinding', 'protocolVersion', 'tenant']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/index.ts new file mode 100644 index 0000000000..6c2d8463b1 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/index.ts @@ -0,0 +1,21 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import urlRequiredLint from './url--required.ts'; +import protocolBindingRequiredLint from './protocol-binding--required.ts'; +import protocolVersionRequiredLint from './protocol-version--required.ts'; +import urlLint from './url--type.ts'; +import protocolBindingLint from './protocol-binding--type.ts'; +import protocolVersionLint from './protocol-version--type.ts'; +import tenantLint from './tenant--type.ts'; + +const lints = [ + allowedFieldsLint, + urlRequiredLint, + protocolBindingRequiredLint, + protocolVersionRequiredLint, + urlLint, + protocolBindingLint, + protocolVersionLint, + tenantLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--required.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--required.ts new file mode 100644 index 0000000000..15f74c22f2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const protocolBindingRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_BINDING_REQUIRED, + source: 'apilint', + message: "should always have a 'protocolBinding' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['protocolBinding'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'protocolBinding' field", + action: 'addChild', + snippetYaml: "protocolBinding: ''\n", + snippetJson: '"protocolBinding": "",\n', + }, + ], + }, +}; + +export default protocolBindingRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--type.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--type.ts new file mode 100644 index 0000000000..becd419feb --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-binding--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_BINDING_TYPE, + source: 'apilint', + message: "'protocolBinding' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'protocolBinding', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--required.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--required.ts new file mode 100644 index 0000000000..ff291479f2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const protocolVersionRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_VERSION_REQUIRED, + source: 'apilint', + message: "should always have a 'protocolVersion' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['protocolVersion'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'protocolVersion' field", + action: 'addChild', + snippetYaml: "protocolVersion: ''\n", + snippetJson: '"protocolVersion": "",\n', + }, + ], + }, +}; + +export default protocolVersionRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--type.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--type.ts new file mode 100644 index 0000000000..afff3a5880 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/protocol-version--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_VERSION_TYPE, + source: 'apilint', + message: "'protocolVersion' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'protocolVersion', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/tenant--type.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/tenant--type.ts new file mode 100644 index 0000000000..37bdc8522d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/tenant--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_TENANT_TYPE, + source: 'apilint', + message: "'tenant' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'tenant', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--required.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--required.ts new file mode 100644 index 0000000000..7e1a323753 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const urlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'url' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['url'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'url' field", + action: 'addChild', + snippetYaml: "url: ''\n", + snippetJson: '"url": "",\n', + }, + ], + }, +}; + +export default urlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--type.ts b/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--type.ts new file mode 100644 index 0000000000..cf164a0252 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/lint/url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_INTERFACE_FIELD_URL_TYPE, + source: 'apilint', + message: "'url' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'url', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-interface/meta.ts b/packages/apidom-ls/src/config/a2a/agent-interface/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-interface/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/completion.ts b/packages/apidom-ls/src/config/a2a/agent-provider/completion.ts new file mode 100644 index 0000000000..c861604c23 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/completion.ts @@ -0,0 +1,34 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'organization', + insertText: 'organization', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: "The name of the agent provider's organization." }, + targetSpecs: A2A1, + }, + { + label: 'url', + insertText: 'url', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: "A URL for the provider's website or documentation.", + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-provider/documentation.ts new file mode 100644 index 0000000000..da7a2d3ad7 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/documentation.ts @@ -0,0 +1,20 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentProvider fields. + * See [AgentProvider](https://a2a-protocol.org/latest/specification/#agentprovider). + */ +const documentation = [ + { + target: 'organization', + docs: 'The name of the agent provider\'s organization (string, required). Example: `"Google"`.', + targetSpecs: A2A1, + }, + { + target: 'url', + docs: "A URL for the agent provider's website or relevant documentation (string, required).", + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/allowed-fields.ts new file mode 100644 index 0000000000..1529bd6b0d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['organization', 'url']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/index.ts new file mode 100644 index 0000000000..7f6b43a225 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/index.ts @@ -0,0 +1,15 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import organizationRequiredLint from './organization--required.ts'; +import urlRequiredLint from './url--required.ts'; +import organizationLint from './organization--type.ts'; +import urlLint from './url--type.ts'; + +const lints = [ + allowedFieldsLint, + organizationRequiredLint, + urlRequiredLint, + organizationLint, + urlLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--required.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--required.ts new file mode 100644 index 0000000000..7fca9e938f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const organizationRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_PROVIDER_FIELD_ORGANIZATION_REQUIRED, + source: 'apilint', + message: "should always have an 'organization' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['organization'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'organization' field", + action: 'addChild', + snippetYaml: "organization: ''\n", + snippetJson: '"organization": "",\n', + }, + ], + }, +}; + +export default organizationRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--type.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--type.ts new file mode 100644 index 0000000000..1701d6e8a7 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/organization--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_PROVIDER_FIELD_ORGANIZATION_TYPE, + source: 'apilint', + message: "'organization' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'organization', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--required.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--required.ts new file mode 100644 index 0000000000..62f7ba7e8e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const urlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_PROVIDER_FIELD_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'url' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['url'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'url' field", + action: 'addChild', + snippetYaml: "url: ''\n", + snippetJson: '"url": "",\n', + }, + ], + }, +}; + +export default urlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--type.ts b/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--type.ts new file mode 100644 index 0000000000..00c8261cc6 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/lint/url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_PROVIDER_FIELD_URL_TYPE, + source: 'apilint', + message: "'url' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'url', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-provider/meta.ts b/packages/apidom-ls/src/config/a2a/agent-provider/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-provider/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/completion.ts b/packages/apidom-ls/src/config/a2a/agent-skill/completion.ts new file mode 100644 index 0000000000..848f887a40 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/completion.ts @@ -0,0 +1,50 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const skillField = ( + label: string, + format: CompletionFormat, + docs: string, +): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + skillField('id', CompletionFormat.QUOTED, 'Unique identifier for the skill.'), + skillField('name', CompletionFormat.QUOTED, 'Human-readable name for the skill.'), + skillField('description', CompletionFormat.QUOTED, 'Detailed description of the skill.'), + skillField('tags', CompletionFormat.ARRAY, "Keywords describing the skill's capabilities."), + skillField( + 'examples', + CompletionFormat.ARRAY, + 'Example prompts or scenarios this skill can handle.', + ), + skillField( + 'inputModes', + CompletionFormat.ARRAY, + "Supported input media types for this skill, overriding the agent's defaults.", + ), + skillField( + 'outputModes', + CompletionFormat.ARRAY, + "Supported output media types for this skill, overriding the agent's defaults.", + ), + skillField( + 'securityRequirements', + CompletionFormat.ARRAY, + 'Security schemes necessary for this skill.', + ), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/documentation.ts b/packages/apidom-ls/src/config/a2a/agent-skill/documentation.ts new file mode 100644 index 0000000000..26fbb9545b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/documentation.ts @@ -0,0 +1,50 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AgentSkill fields. + * See [AgentSkill](https://a2a-protocol.org/latest/specification/#445-agentskill). + */ +const documentation = [ + { + target: 'id', + docs: 'A unique identifier for the skill (string, required).', + targetSpecs: A2A1, + }, + { + target: 'name', + docs: 'A human-readable name for the skill (string, required).', + targetSpecs: A2A1, + }, + { + target: 'description', + docs: 'A detailed description of the skill (string, required).', + targetSpecs: A2A1, + }, + { + target: 'tags', + docs: "Array of keywords describing the skill's capabilities (array of strings, required).", + targetSpecs: A2A1, + }, + { + target: 'examples', + docs: 'Example prompts or scenarios this skill can handle (array of strings).', + targetSpecs: A2A1, + }, + { + target: 'inputModes', + docs: "Supported input media types for this skill, overriding the agent's defaults (array of strings).", + targetSpecs: A2A1, + }, + { + target: 'outputModes', + docs: "Supported output media types for this skill, overriding the agent's defaults (array of strings).", + targetSpecs: A2A1, + }, + { + target: 'securityRequirements', + docs: 'Array of [Security Requirement Objects](https://a2a-protocol.org/latest/definitions/#security-requirement) — specifies which security schemes clients must satisfy to use this skill.', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/allowed-fields.ts new file mode 100644 index 0000000000..7ae0201d8a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/allowed-fields.ts @@ -0,0 +1,29 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [ + [ + 'id', + 'name', + 'description', + 'tags', + 'examples', + 'inputModes', + 'outputModes', + 'securityRequirements', + ], + ], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--required.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--required.ts new file mode 100644 index 0000000000..8d9c4f1315 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_DESCRIPTION_REQUIRED, + source: 'apilint', + message: "should always have a 'description' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['description'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'description' field", + action: 'addChild', + snippetYaml: "description: ''\n", + snippetJson: '"description": "",\n', + }, + ], + }, +}; + +export default descriptionRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--type.ts new file mode 100644 index 0000000000..acb87cde9c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/examples--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/examples--type.ts new file mode 100644 index 0000000000..76078571bf --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/examples--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_EXAMPLES_TYPE, + source: 'apilint', + message: "'examples' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'examples', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--required.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--required.ts new file mode 100644 index 0000000000..e356d341b1 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const idRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_ID_REQUIRED, + source: 'apilint', + message: "should always have an 'id' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['id'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'id' field", + action: 'addChild', + snippetYaml: "id: ''\n", + snippetJson: '"id": "",\n', + }, + ], + }, +}; + +export default idRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--type.ts new file mode 100644 index 0000000000..6f1d30c64e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/id--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_ID_TYPE, + source: 'apilint', + message: "'id' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'id', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/index.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/index.ts new file mode 100644 index 0000000000..62f68589c0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/index.ts @@ -0,0 +1,31 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import idRequiredLint from './id--required.ts'; +import nameRequiredLint from './name--required.ts'; +import descriptionRequiredLint from './description--required.ts'; +import tagsRequiredLint from './tags--required.ts'; +import idLint from './id--type.ts'; +import nameLint from './name--type.ts'; +import descriptionLint from './description--type.ts'; +import tagsLint from './tags--type.ts'; +import examplesLint from './examples--type.ts'; +import inputModesLint from './input-modes--type.ts'; +import outputModesLint from './output-modes--type.ts'; +import securityRequirementsLint from './security-requirements--type.ts'; + +const lints = [ + allowedFieldsLint, + idRequiredLint, + nameRequiredLint, + descriptionRequiredLint, + tagsRequiredLint, + idLint, + nameLint, + descriptionLint, + tagsLint, + examplesLint, + inputModesLint, + outputModesLint, + securityRequirementsLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/input-modes--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/input-modes--type.ts new file mode 100644 index 0000000000..a85580a3b2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/input-modes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_INPUT_MODES_TYPE, + source: 'apilint', + message: "'inputModes' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'inputModes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--required.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--required.ts new file mode 100644 index 0000000000..d1aca9eaee --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const nameRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_NAME_REQUIRED, + source: 'apilint', + message: "should always have a 'name' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['name'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'name' field", + action: 'addChild', + snippetYaml: "name: ''\n", + snippetJson: '"name": "",\n', + }, + ], + }, +}; + +export default nameRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--type.ts new file mode 100644 index 0000000000..4e9882772d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/name--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_NAME_TYPE, + source: 'apilint', + message: "'name' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'name', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/output-modes--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/output-modes--type.ts new file mode 100644 index 0000000000..cc7edc37fb --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/output-modes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_OUTPUT_MODES_TYPE, + source: 'apilint', + message: "'outputModes' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'outputModes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/security-requirements--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/security-requirements--type.ts new file mode 100644 index 0000000000..1c3a0f7a5a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/security-requirements--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_SECURITY_REQUIREMENTS_TYPE, + source: 'apilint', + message: "'securityRequirements' must be an array of Security Requirement Objects", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintArrayOfElementsOrClasses', + linterParams: [['securityRequirement']], + marker: 'value', + target: 'securityRequirements', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--required.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--required.ts new file mode 100644 index 0000000000..0f7cc5adfc --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const tagsRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_TAGS_REQUIRED, + source: 'apilint', + message: "should always have a 'tags' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['tags'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'tags' field", + action: 'addChild', + snippetYaml: 'tags:\n - \n', + snippetJson: '"tags": [],\n', + }, + ], + }, +}; + +export default tagsRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--type.ts b/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--type.ts new file mode 100644 index 0000000000..abaf291762 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/lint/tags--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AGENT_SKILL_FIELD_TAGS_TYPE, + source: 'apilint', + message: "'tags' must be an array", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['array'], + marker: 'value', + target: 'tags', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/agent-skill/meta.ts b/packages/apidom-ls/src/config/a2a/agent-skill/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/agent-skill/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/completion.ts new file mode 100644 index 0000000000..0dcfe85894 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/completion.ts @@ -0,0 +1,51 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'An optional description for the security scheme (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'location', + insertText: 'location', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'The location of the API key. Valid values are "query", "header", or "cookie" (string, required).', + }, + targetSpecs: A2A1, + }, + { + label: 'name', + insertText: 'name', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'The name of the header, query, or cookie parameter to be used (string, required).', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/documentation.ts new file mode 100644 index 0000000000..2519345444 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/documentation.ts @@ -0,0 +1,25 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 APIKeySecurityScheme fields. + * See [APIKeySecurityScheme](https://a2a-protocol.org/latest/specification/#452-apikeysecurityscheme). + */ +const documentation = [ + { + target: 'description', + docs: 'An optional description for the security scheme (string).', + targetSpecs: A2A1, + }, + { + target: 'location', + docs: 'The location of the API key. Valid values are "query", "header", or "cookie" (string, required).', + targetSpecs: A2A1, + }, + { + target: 'name', + docs: 'The name of the header, query, or cookie parameter to be used (string, required).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..9b9fbd5856 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['description', 'name', 'location']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/description--type.ts new file mode 100644 index 0000000000..65b2fd7f9e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_API_KEY_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/index.ts new file mode 100644 index 0000000000..8c059390bd --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/index.ts @@ -0,0 +1,17 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import locationRequiredLint from './location--required.ts'; +import nameRequiredLint from './name--required.ts'; +import nameTypeLint from './name--type.ts'; +import descriptionTypeLint from './description--type.ts'; +import locationValueLint from './location--value.ts'; + +const lints = [ + allowedFieldsLint, + locationRequiredLint, + nameRequiredLint, + nameTypeLint, + descriptionTypeLint, + locationValueLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--required.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--required.ts new file mode 100644 index 0000000000..9b05edc0ce --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const locationRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_API_KEY_SECURITY_SCHEME_FIELD_LOCATION_REQUIRED, + source: 'apilint', + message: "should always have a 'location' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['location'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'location' field", + action: 'addChild', + snippetYaml: "location: ''\n", + snippetJson: '"location": "",\n', + }, + ], + }, +}; + +export default locationRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--value.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--value.ts new file mode 100644 index 0000000000..525eb43fa2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/location--value.ts @@ -0,0 +1,20 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const locationValueLint: LinterMeta = { + code: ApilintCodes.A2A1_API_KEY_SECURITY_SCHEME_FIELD_LOCATION_VALUE, + source: 'apilint', + message: "'location' must be one of allowed values: query, header, cookie", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintValueOrArray', + linterParams: [['query', 'header', 'cookie']], + marker: 'value', + target: 'location', + data: {}, + targetSpecs: A2A1, +}; + +export default locationValueLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--required.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--required.ts new file mode 100644 index 0000000000..cf2a765c49 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const nameRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_API_KEY_SECURITY_SCHEME_FIELD_NAME_REQUIRED, + source: 'apilint', + message: "should always have a 'name' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['name'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'name' field", + action: 'addChild', + snippetYaml: "name: ''\n", + snippetJson: '"name": "",\n', + }, + ], + }, +}; + +export default nameRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--type.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--type.ts new file mode 100644 index 0000000000..2947a7f276 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/lint/name--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const nameTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_API_KEY_SECURITY_SCHEME_FIELD_NAME_TYPE, + source: 'apilint', + message: "'name' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'name', + targetSpecs: A2A1, +}; + +export default nameTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/api-key-security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/api-key-security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/completion.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/completion.ts new file mode 100644 index 0000000000..870ddb3840 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/completion.ts @@ -0,0 +1,47 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const flowField = ( + label: string, + format: CompletionFormat, + docs: string, +): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + flowField( + 'authorizationUrl', + CompletionFormat.QUOTED, + 'The authorization URL to be used for this flow.', + ), + flowField('tokenUrl', CompletionFormat.QUOTED, 'The token URL to be used for this flow.'), + flowField( + 'refreshUrl', + CompletionFormat.QUOTED, + 'The URL to be used for obtaining refresh tokens.', + ), + flowField( + 'pkceRequired', + CompletionFormat.UNQUOTED, + 'Whether PKCE (Proof Key for Code Exchange) is required for this flow.', + ), + flowField( + 'scopes', + CompletionFormat.OBJECT, + 'Available scopes for the OAuth2 security scheme. A map of scope name to short description.', + ), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/documentation.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/documentation.ts new file mode 100644 index 0000000000..b36e7c3961 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/documentation.ts @@ -0,0 +1,35 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 AuthorizationCodeOAuthFlow fields. + * See [AuthorizationCodeOAuthFlow](https://a2a-protocol.org/latest/specification/#458-authorizationcodeoauthflow). + */ +const documentation = [ + { + target: 'authorizationUrl', + docs: 'The authorization URL to be used for this flow (string, required).', + targetSpecs: A2A1, + }, + { + target: 'tokenUrl', + docs: 'The token URL to be used for this flow (string, required).', + targetSpecs: A2A1, + }, + { + target: 'refreshUrl', + docs: 'The URL to be used for obtaining refresh tokens (string).', + targetSpecs: A2A1, + }, + { + target: 'scopes', + docs: 'A map of scope name to description available for the OAuth2 security scheme (required).', + targetSpecs: A2A1, + }, + { + target: 'pkceRequired', + docs: 'Indicates if PKCE (RFC 7636) is required for this flow (boolean).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/allowed-fields.ts new file mode 100644 index 0000000000..cf7a9a3d69 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['authorizationUrl', 'tokenUrl', 'refreshUrl', 'pkceRequired', 'scopes']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--required.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--required.ts new file mode 100644 index 0000000000..75cc5cadec --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const authorizationUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_AUTHORIZATION_URL_REQUIRED, + source: 'apilint', + message: "should always have an 'authorizationUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['authorizationUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'authorizationUrl' field", + action: 'addChild', + snippetYaml: "authorizationUrl: ''\n", + snippetJson: '"authorizationUrl": "",\n', + }, + ], + }, +}; + +export default authorizationUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--type.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--type.ts new file mode 100644 index 0000000000..82b4af4f70 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/authorization-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_AUTHORIZATION_URL_TYPE, + source: 'apilint', + message: "'authorizationUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'authorizationUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/index.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/index.ts new file mode 100644 index 0000000000..d8ab9ea671 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/index.ts @@ -0,0 +1,23 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import authorizationUrlRequiredLint from './authorization-url--required.ts'; +import tokenUrlRequiredLint from './token-url--required.ts'; +import scopesRequiredLint from './scopes--required.ts'; +import authorizationUrlLint from './authorization-url--type.ts'; +import tokenUrlLint from './token-url--type.ts'; +import refreshUrlLint from './refresh-url--type.ts'; +import pkceRequiredLint from './pkce-required--type.ts'; +import scopesLint from './scopes--type.ts'; + +const lints = [ + allowedFieldsLint, + authorizationUrlRequiredLint, + tokenUrlRequiredLint, + scopesRequiredLint, + authorizationUrlLint, + tokenUrlLint, + refreshUrlLint, + pkceRequiredLint, + scopesLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/pkce-required--type.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/pkce-required--type.ts new file mode 100644 index 0000000000..28ef1144bd --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/pkce-required--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_PKCE_REQUIRED_TYPE, + source: 'apilint', + message: "'pkceRequired' must be a boolean", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['boolean'], + marker: 'value', + target: 'pkceRequired', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/refresh-url--type.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/refresh-url--type.ts new file mode 100644 index 0000000000..ce06ccac07 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/refresh-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE, + source: 'apilint', + message: "'refreshUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'refreshUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--required.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--required.ts new file mode 100644 index 0000000000..512ab6c4e0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const scopesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_SCOPES_REQUIRED, + source: 'apilint', + message: "should always have a 'scopes' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['scopes'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, +}; + +export default scopesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--type.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--type.ts new file mode 100644 index 0000000000..0df14bdf5b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/scopes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_SCOPES_TYPE, + source: 'apilint', + message: "'scopes' must be an object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['object'], + marker: 'value', + target: 'scopes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--required.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--required.ts new file mode 100644 index 0000000000..b0bfeb0975 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const tokenUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'tokenUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['tokenUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'tokenUrl' field", + action: 'addChild', + snippetYaml: "tokenUrl: ''\n", + snippetJson: '"tokenUrl": "",\n', + }, + ], + }, +}; + +export default tokenUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--type.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--type.ts new file mode 100644 index 0000000000..29a1b4c43d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/lint/token-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE, + source: 'apilint', + message: "'tokenUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'tokenUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/meta.ts b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/authorization-code-oauth-flow/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/completion.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/completion.ts new file mode 100644 index 0000000000..cd69f1880d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/completion.ts @@ -0,0 +1,37 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const flowField = ( + label: string, + format: CompletionFormat, + docs: string, +): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + flowField('tokenUrl', CompletionFormat.QUOTED, 'The token URL to be used for this flow.'), + flowField( + 'refreshUrl', + CompletionFormat.QUOTED, + 'The URL to be used for obtaining refresh tokens.', + ), + flowField( + 'scopes', + CompletionFormat.OBJECT, + 'Available scopes for the OAuth2 security scheme. A map of scope name to short description.', + ), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/documentation.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/documentation.ts new file mode 100644 index 0000000000..25897fc458 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/documentation.ts @@ -0,0 +1,25 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 ClientCredentialsOAuthFlow fields. + * See [ClientCredentialsOAuthFlow](https://a2a-protocol.org/latest/specification/#459-clientcredentialsoauthflow). + */ +const documentation = [ + { + target: 'tokenUrl', + docs: 'The token URL to be used for this flow (string, required).', + targetSpecs: A2A1, + }, + { + target: 'refreshUrl', + docs: 'The URL to be used for obtaining refresh tokens (string).', + targetSpecs: A2A1, + }, + { + target: 'scopes', + docs: 'A map of scope name to description available for the OAuth2 security scheme (required).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/allowed-fields.ts new file mode 100644 index 0000000000..2c7a29e77f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['tokenUrl', 'refreshUrl', 'scopes']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/index.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/index.ts new file mode 100644 index 0000000000..094482622e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/index.ts @@ -0,0 +1,17 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import tokenUrlRequiredLint from './token-url--required.ts'; +import scopesRequiredLint from './scopes--required.ts'; +import tokenUrlLint from './token-url--type.ts'; +import refreshUrlLint from './refresh-url--type.ts'; +import scopesLint from './scopes--type.ts'; + +const lints = [ + allowedFieldsLint, + tokenUrlRequiredLint, + scopesRequiredLint, + tokenUrlLint, + refreshUrlLint, + scopesLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/refresh-url--type.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/refresh-url--type.ts new file mode 100644 index 0000000000..6ee21f83ca --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/refresh-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE, + source: 'apilint', + message: "'refreshUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'refreshUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--required.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--required.ts new file mode 100644 index 0000000000..d00c7c8dc9 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const scopesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_SCOPES_REQUIRED, + source: 'apilint', + message: "should always have a 'scopes' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['scopes'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, +}; + +export default scopesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--type.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--type.ts new file mode 100644 index 0000000000..9d62e2924e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/scopes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_SCOPES_TYPE, + source: 'apilint', + message: "'scopes' must be an object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['object'], + marker: 'value', + target: 'scopes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--required.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--required.ts new file mode 100644 index 0000000000..aceacc9639 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const tokenUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'tokenUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['tokenUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'tokenUrl' field", + action: 'addChild', + snippetYaml: "tokenUrl: ''\n", + snippetJson: '"tokenUrl": "",\n', + }, + ], + }, +}; + +export default tokenUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--type.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--type.ts new file mode 100644 index 0000000000..0e5d7fb9b9 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/lint/token-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE, + source: 'apilint', + message: "'tokenUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'tokenUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/meta.ts b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/client-credentials-oauth-flow/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/config.ts b/packages/apidom-ls/src/config/a2a/config.ts new file mode 100644 index 0000000000..4a2fca7e9d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/config.ts @@ -0,0 +1,52 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import a2a1Meta from './a2a1/meta.ts'; +import agentCapabilitiesMeta from './agent-capabilities/meta.ts'; +import agentSkillMeta from './agent-skill/meta.ts'; +import agentProviderMeta from './agent-provider/meta.ts'; +import agentInterfaceMeta from './agent-interface/meta.ts'; +import securitySchemeMeta from './security-scheme/meta.ts'; +import agentCardSignatureMeta from './agent-card-signature/meta.ts'; +import oauthFlowsMeta from './oauth-flows/meta.ts'; +import authorizationCodeOAuthFlowMeta from './authorization-code-oauth-flow/meta.ts'; +import clientCredentialsOAuthFlowMeta from './client-credentials-oauth-flow/meta.ts'; +import deviceCodeOAuthFlowMeta from './device-code-oauth-flow/meta.ts'; +import agentExtensionMeta from './agent-extension/meta.ts'; +import apiKeySecuritySchemeMeta from './api-key-security-scheme/meta.ts'; +import httpAuthSecuritySchemeMeta from './http-auth-security-scheme/meta.ts'; +import oauth2SecuritySchemeMeta from './oauth2-security-scheme/meta.ts'; +import openIdConnectSecuritySchemeMeta from './open-id-connect-security-scheme/meta.ts'; +import mutualTlsSecuritySchemeMeta from './mutual-tls-security-scheme/meta.ts'; +import ApilintCodes from '../codes.ts'; + +export default { + '*': { + lint: [ + { + code: ApilintCodes.DUPLICATE_KEYS, + source: 'apilint', + message: 'an object cannot contain duplicate keys', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintNoDuplicateKeys', + marker: 'key', + }, + ], + }, + a2aAgentCard1: a2a1Meta, + agentCapabilities: agentCapabilitiesMeta, + agentSkill: agentSkillMeta, + agentProvider: agentProviderMeta, + agentInterface: agentInterfaceMeta, + securityScheme: securitySchemeMeta, + agentCardSignature: agentCardSignatureMeta, + oauthFlows: oauthFlowsMeta, + authorizationCodeOAuthFlow: authorizationCodeOAuthFlowMeta, + clientCredentialsOAuthFlow: clientCredentialsOAuthFlowMeta, + deviceCodeOAuthFlow: deviceCodeOAuthFlowMeta, + agentExtension: agentExtensionMeta, + apiKeySecurityScheme: apiKeySecuritySchemeMeta, + httpAuthSecurityScheme: httpAuthSecuritySchemeMeta, + oauth2SecurityScheme: oauth2SecuritySchemeMeta, + openIdConnectSecurityScheme: openIdConnectSecuritySchemeMeta, + mutualTlsSecurityScheme: mutualTlsSecuritySchemeMeta, +}; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/completion.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/completion.ts new file mode 100644 index 0000000000..dd7304dd3f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/completion.ts @@ -0,0 +1,42 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const flowField = ( + label: string, + format: CompletionFormat, + docs: string, +): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + flowField( + 'deviceAuthorizationUrl', + CompletionFormat.QUOTED, + 'The device authorization endpoint used to obtain a device verification code.', + ), + flowField('tokenUrl', CompletionFormat.QUOTED, 'The token URL to be used for this flow.'), + flowField( + 'refreshUrl', + CompletionFormat.QUOTED, + 'The URL to be used for obtaining refresh tokens.', + ), + flowField( + 'scopes', + CompletionFormat.OBJECT, + 'Available scopes for the OAuth2 security scheme. A map of scope name to short description.', + ), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/documentation.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/documentation.ts new file mode 100644 index 0000000000..6ae3719f5e --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/documentation.ts @@ -0,0 +1,30 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 DeviceCodeOAuthFlow fields (RFC 8628). + * See [DeviceCodeOAuthFlow](https://a2a-protocol.org/latest/specification/#4510-devicecodeoauthflow). + */ +const documentation = [ + { + target: 'deviceAuthorizationUrl', + docs: 'The device authorization endpoint URL (string, required).', + targetSpecs: A2A1, + }, + { + target: 'tokenUrl', + docs: 'The token URL to be used for this flow (string, required).', + targetSpecs: A2A1, + }, + { + target: 'refreshUrl', + docs: 'The URL to be used for obtaining refresh tokens (string).', + targetSpecs: A2A1, + }, + { + target: 'scopes', + docs: 'A map of scope name to description available for the OAuth2 security scheme (required).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/allowed-fields.ts new file mode 100644 index 0000000000..eaae4d306b --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['deviceAuthorizationUrl', 'tokenUrl', 'refreshUrl', 'scopes']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--required.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--required.ts new file mode 100644 index 0000000000..397b987a25 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const deviceAuthorizationUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_DEVICE_AUTHORIZATION_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'deviceAuthorizationUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['deviceAuthorizationUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'deviceAuthorizationUrl' field", + action: 'addChild', + snippetYaml: "deviceAuthorizationUrl: ''\n", + snippetJson: '"deviceAuthorizationUrl": "",\n', + }, + ], + }, +}; + +export default deviceAuthorizationUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--type.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--type.ts new file mode 100644 index 0000000000..f9941a693f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/device-authorization-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_DEVICE_AUTHORIZATION_URL_TYPE, + source: 'apilint', + message: "'deviceAuthorizationUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'deviceAuthorizationUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/index.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/index.ts new file mode 100644 index 0000000000..82ba2f2785 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/index.ts @@ -0,0 +1,21 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import deviceAuthorizationUrlRequiredLint from './device-authorization-url--required.ts'; +import tokenUrlRequiredLint from './token-url--required.ts'; +import scopesRequiredLint from './scopes--required.ts'; +import deviceAuthorizationUrlLint from './device-authorization-url--type.ts'; +import tokenUrlLint from './token-url--type.ts'; +import refreshUrlLint from './refresh-url--type.ts'; +import scopesLint from './scopes--type.ts'; + +const lints = [ + allowedFieldsLint, + deviceAuthorizationUrlRequiredLint, + tokenUrlRequiredLint, + scopesRequiredLint, + deviceAuthorizationUrlLint, + tokenUrlLint, + refreshUrlLint, + scopesLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/refresh-url--type.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/refresh-url--type.ts new file mode 100644 index 0000000000..f02c813388 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/refresh-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE, + source: 'apilint', + message: "'refreshUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'refreshUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--required.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--required.ts new file mode 100644 index 0000000000..a4ab0c5a8f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const scopesRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_SCOPES_REQUIRED, + source: 'apilint', + message: "should always have a 'scopes' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['scopes'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, +}; + +export default scopesRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--type.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--type.ts new file mode 100644 index 0000000000..e3a0f189a4 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/scopes--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_SCOPES_TYPE, + source: 'apilint', + message: "'scopes' must be an object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['object'], + marker: 'value', + target: 'scopes', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--required.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--required.ts new file mode 100644 index 0000000000..b5dd95c755 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const tokenUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED, + source: 'apilint', + message: "should always have a 'tokenUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['tokenUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'tokenUrl' field", + action: 'addChild', + snippetYaml: "tokenUrl: ''\n", + snippetJson: '"tokenUrl": "",\n', + }, + ], + }, +}; + +export default tokenUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--type.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--type.ts new file mode 100644 index 0000000000..e7ec5af320 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/lint/token-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE, + source: 'apilint', + message: "'tokenUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'tokenUrl', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/meta.ts b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/device-code-oauth-flow/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/completion.ts new file mode 100644 index 0000000000..14c7f56e94 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/completion.ts @@ -0,0 +1,52 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'An optional description for the security scheme (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'scheme', + insertText: 'scheme', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'The name of the HTTP Authentication scheme to be used in the Authorization header, as defined in RFC 7235 (e.g. "Bearer") (string, required).', + }, + targetSpecs: A2A1, + }, + { + label: 'bearerFormat', + insertText: 'bearerFormat', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'A hint to the client to identify how the bearer token is formatted (e.g. "JWT") (string).', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/documentation.ts new file mode 100644 index 0000000000..d423229bd2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/documentation.ts @@ -0,0 +1,25 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 HTTPAuthSecurityScheme fields. + * See [HTTPAuthSecurityScheme](https://a2a-protocol.org/latest/specification/#453-httpauthsecurityscheme). + */ +const documentation = [ + { + target: 'description', + docs: 'An optional description for the security scheme (string).', + targetSpecs: A2A1, + }, + { + target: 'scheme', + docs: 'The name of the HTTP Authentication scheme to be used in the Authorization header, as defined in RFC 7235 (e.g. "Bearer") (string, required).', + targetSpecs: A2A1, + }, + { + target: 'bearerFormat', + docs: 'A hint to the client to identify how the bearer token is formatted (e.g. "JWT") (string).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..21986d0cb3 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['description', 'scheme', 'bearerFormat']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/bearer-format--type.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/bearer-format--type.ts new file mode 100644 index 0000000000..ef4edf9a8d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/bearer-format--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const bearerFormatTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_BEARER_FORMAT_TYPE, + source: 'apilint', + message: "'bearerFormat' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'bearerFormat', + targetSpecs: A2A1, +}; + +export default bearerFormatTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/description--type.ts new file mode 100644 index 0000000000..bf3ab3b133 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/index.ts new file mode 100644 index 0000000000..6e81fb4778 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/index.ts @@ -0,0 +1,15 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import schemeRequiredLint from './scheme--required.ts'; +import schemeTypeLint from './scheme--type.ts'; +import descriptionTypeLint from './description--type.ts'; +import bearerFormatTypeLint from './bearer-format--type.ts'; + +const lints = [ + allowedFieldsLint, + schemeRequiredLint, + schemeTypeLint, + descriptionTypeLint, + bearerFormatTypeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--required.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--required.ts new file mode 100644 index 0000000000..1d794bd9ca --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const schemeRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_SCHEME_REQUIRED, + source: 'apilint', + message: "should always have a 'scheme' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['scheme'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'scheme' field", + action: 'addChild', + snippetYaml: "scheme: ''\n", + snippetJson: '"scheme": "",\n', + }, + ], + }, +}; + +export default schemeRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--type.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--type.ts new file mode 100644 index 0000000000..a3a0c1f48d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/lint/scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const schemeTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_SCHEME_TYPE, + source: 'apilint', + message: "'scheme' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'scheme', + targetSpecs: A2A1, +}; + +export default schemeTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/http-auth-security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/completion.ts new file mode 100644 index 0000000000..278f4a903d --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/completion.ts @@ -0,0 +1,24 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'An optional description for the security scheme (string).', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/documentation.ts new file mode 100644 index 0000000000..e8543e74fe --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/documentation.ts @@ -0,0 +1,15 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 MutualTlsSecurityScheme fields. + * See [MutualTlsSecurityScheme](https://a2a-protocol.org/latest/specification/#456-mutualtlssecurityscheme). + */ +const documentation = [ + { + target: 'description', + docs: 'An optional description for the security scheme (string).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..b8015bba09 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['description']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/description--type.ts new file mode 100644 index 0000000000..0035fed318 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_MUTUAL_TLS_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/index.ts new file mode 100644 index 0000000000..58f6a012e8 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/lint/index.ts @@ -0,0 +1,6 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import descriptionTypeLint from './description--type.ts'; + +const lints = [allowedFieldsLint, descriptionTypeLint]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/mutual-tls-security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/completion.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/completion.ts new file mode 100644 index 0000000000..0cd1da1f20 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/completion.ts @@ -0,0 +1,25 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const flowField = (label: string, docs: string): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + flowField('authorizationCode', 'OAuth 2.0 Authorization Code flow configuration.'), + flowField('clientCredentials', 'OAuth 2.0 Client Credentials flow configuration.'), + flowField('deviceCode', 'OAuth 2.0 Device Authorization Grant flow configuration.'), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/documentation.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/documentation.ts new file mode 100644 index 0000000000..fa01754834 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/documentation.ts @@ -0,0 +1,26 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 OAuthFlows fields. OAuthFlows must contain + * exactly one of the flow configurations below. + * See [OAuthFlows](https://a2a-protocol.org/latest/specification/#457-oauthflows). + */ +const documentation = [ + { + target: 'authorizationCode', + docs: '[Authorization Code OAuth Flow](https://a2a-protocol.org/latest/specification/#458-authorizationcodeoauthflow) — configuration for the OAuth Authorization Code flow.', + targetSpecs: A2A1, + }, + { + target: 'clientCredentials', + docs: '[Client Credentials OAuth Flow](https://a2a-protocol.org/latest/specification/#459-clientcredentialsoauthflow) — configuration for the OAuth Client Credentials flow.', + targetSpecs: A2A1, + }, + { + target: 'deviceCode', + docs: '[Device Code OAuth Flow](https://a2a-protocol.org/latest/specification/#4510-devicecodeoauthflow) — configuration for the OAuth Device Code flow (RFC 8628).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/allowed-fields.ts new file mode 100644 index 0000000000..5642d509bb --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['authorizationCode', 'clientCredentials', 'implicit', 'password', 'deviceCode']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/lint/authorization-code--type.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/authorization-code--type.ts new file mode 100644 index 0000000000..35afe89544 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/authorization-code--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH_FLOWS_FIELD_AUTHORIZATION_CODE_TYPE, + source: 'apilint', + message: "'authorizationCode' must be an Authorization Code OAuth Flow Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['authorizationCodeOAuthFlow'], + marker: 'value', + target: 'authorizationCode', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/lint/client-credentials--type.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/client-credentials--type.ts new file mode 100644 index 0000000000..4eaeb15e85 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/client-credentials--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH_FLOWS_FIELD_CLIENT_CREDENTIALS_TYPE, + source: 'apilint', + message: "'clientCredentials' must be a Client Credentials OAuth Flow Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['clientCredentialsOAuthFlow'], + marker: 'value', + target: 'clientCredentials', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/lint/device-code--type.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/device-code--type.ts new file mode 100644 index 0000000000..1c7eae00e9 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/device-code--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH_FLOWS_FIELD_DEVICE_CODE_TYPE, + source: 'apilint', + message: "'deviceCode' must be a Device Code OAuth Flow Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['deviceCodeOAuthFlow'], + marker: 'value', + target: 'deviceCode', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/lint/index.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/index.ts new file mode 100644 index 0000000000..bd71828554 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/lint/index.ts @@ -0,0 +1,8 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import authorizationCodeLint from './authorization-code--type.ts'; +import clientCredentialsLint from './client-credentials--type.ts'; +import deviceCodeLint from './device-code--type.ts'; + +const lints = [allowedFieldsLint, authorizationCodeLint, clientCredentialsLint, deviceCodeLint]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/oauth-flows/meta.ts b/packages/apidom-ls/src/config/a2a/oauth-flows/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth-flows/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/completion.ts new file mode 100644 index 0000000000..71ed6f6c49 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/completion.ts @@ -0,0 +1,52 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'An optional description for the security scheme (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'flows', + insertText: 'flows', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'An object containing configuration information for the supported OAuth 2.0 flows (required).', + }, + targetSpecs: A2A1, + }, + { + label: 'oauth2MetadataUrl', + insertText: 'oauth2MetadataUrl', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + 'URL to the OAuth2 authorization server metadata (RFC 8414). TLS is required (string).', + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/documentation.ts new file mode 100644 index 0000000000..2bb5761b81 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/documentation.ts @@ -0,0 +1,25 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 OAuth2SecurityScheme fields. + * See [OAuth2SecurityScheme](https://a2a-protocol.org/latest/specification/#454-oauth2securityscheme). + */ +const documentation = [ + { + target: 'description', + docs: 'An optional description for the security scheme (string).', + targetSpecs: A2A1, + }, + { + target: 'flows', + docs: 'An object containing configuration information for the supported OAuth 2.0 flows (required).', + targetSpecs: A2A1, + }, + { + target: 'oauth2MetadataUrl', + docs: 'URL to the OAuth2 authorization server metadata (RFC 8414). TLS is required (string).', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..485cc21b1a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['description', 'flows', 'oauth2MetadataUrl']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/description--type.ts new file mode 100644 index 0000000000..7bf4c2131a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH2_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--required.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--required.ts new file mode 100644 index 0000000000..2cd6b633d5 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const flowsRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH2_SECURITY_SCHEME_FIELD_FLOWS_REQUIRED, + source: 'apilint', + message: "should always have a 'flows' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['flows'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'flows' field", + action: 'addChild', + snippetYaml: 'flows: \n \n', + snippetJson: '"flows": {\n \n },\n', + }, + ], + }, +}; + +export default flowsRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--type.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--type.ts new file mode 100644 index 0000000000..cf5650ea8a --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/flows--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const flowsTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH2_SECURITY_SCHEME_FIELD_FLOWS_TYPE, + source: 'apilint', + message: "'flows' must be an OAuth Flows Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['oauthFlows'], + marker: 'value', + target: 'flows', + targetSpecs: A2A1, +}; + +export default flowsTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/index.ts new file mode 100644 index 0000000000..aee8b28e75 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/index.ts @@ -0,0 +1,15 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import flowsRequiredLint from './flows--required.ts'; +import flowsTypeLint from './flows--type.ts'; +import descriptionTypeLint from './description--type.ts'; +import oauth2MetadataUrlTypeLint from './oauth2-metadata-url--type.ts'; + +const lints = [ + allowedFieldsLint, + flowsRequiredLint, + flowsTypeLint, + descriptionTypeLint, + oauth2MetadataUrlTypeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/oauth2-metadata-url--type.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/oauth2-metadata-url--type.ts new file mode 100644 index 0000000000..7c4d14acf0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/lint/oauth2-metadata-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const oauth2MetadataUrlTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_OAUTH2_SECURITY_SCHEME_FIELD_OAUTH2_METADATA_URL_TYPE, + source: 'apilint', + message: "'oauth2MetadataUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'oauth2MetadataUrl', + targetSpecs: A2A1, +}; + +export default oauth2MetadataUrlTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/oauth2-security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/completion.ts new file mode 100644 index 0000000000..60785e0f97 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/completion.ts @@ -0,0 +1,38 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const completion: ApidomCompletionItem[] = [ + { + label: 'description', + insertText: 'description', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: 'An optional description for the security scheme (string).', + }, + targetSpecs: A2A1, + }, + { + label: 'openIdConnectUrl', + insertText: 'openIdConnectUrl', + kind: 14, + format: CompletionFormat.QUOTED, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { + kind: 'markdown', + value: + "The OpenID Connect Discovery URL for the OIDC provider's metadata (string, required).", + }, + targetSpecs: A2A1, + }, +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/documentation.ts new file mode 100644 index 0000000000..1e36d231d4 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/documentation.ts @@ -0,0 +1,20 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 OpenIdConnectSecurityScheme fields. + * See [OpenIdConnectSecurityScheme](https://a2a-protocol.org/latest/specification/#455-openidconnectsecurityscheme). + */ +const documentation = [ + { + target: 'description', + docs: 'An optional description for the security scheme (string).', + targetSpecs: A2A1, + }, + { + target: 'openIdConnectUrl', + docs: "The OpenID Connect Discovery URL for the OIDC provider's metadata (string, required).", + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..7a0cde7bd9 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/allowed-fields.ts @@ -0,0 +1,18 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [['description', 'openIdConnectUrl']], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/description--type.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/description--type.ts new file mode 100644 index 0000000000..1adf63c93c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/description--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const descriptionTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE, + source: 'apilint', + message: "'description' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'description', + targetSpecs: A2A1, +}; + +export default descriptionTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/index.ts new file mode 100644 index 0000000000..4d63904727 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/index.ts @@ -0,0 +1,13 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import openIdConnectUrlRequiredLint from './open-id-connect-url--required.ts'; +import openIdConnectUrlTypeLint from './open-id-connect-url--type.ts'; +import descriptionTypeLint from './description--type.ts'; + +const lints = [ + allowedFieldsLint, + openIdConnectUrlRequiredLint, + openIdConnectUrlTypeLint, + descriptionTypeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--required.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--required.ts new file mode 100644 index 0000000000..20677692e0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--required.ts @@ -0,0 +1,28 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const openIdConnectUrlRequiredLint: LinterMeta = { + code: ApilintCodes.A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_URL_REQUIRED, + source: 'apilint', + message: "should always have an 'openIdConnectUrl' field", + severity: DiagnosticSeverity.Error, + linterFunction: 'hasRequiredField', + linterParams: ['openIdConnectUrl'], + marker: 'key', + targetSpecs: A2A1, + data: { + quickFix: [ + { + message: "add 'openIdConnectUrl' field", + action: 'addChild', + snippetYaml: "openIdConnectUrl: ''\n", + snippetJson: '"openIdConnectUrl": "",\n', + }, + ], + }, +}; + +export default openIdConnectUrlRequiredLint; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--type.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--type.ts new file mode 100644 index 0000000000..34331880eb --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/lint/open-id-connect-url--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const openIdConnectUrlTypeLint: LinterMeta = { + code: ApilintCodes.A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_URL_TYPE, + source: 'apilint', + message: "'openIdConnectUrl' must be a string", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintType', + linterParams: ['string'], + marker: 'value', + target: 'openIdConnectUrl', + targetSpecs: A2A1, +}; + +export default openIdConnectUrlTypeLint; diff --git a/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/open-id-connect-security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/completion.ts b/packages/apidom-ls/src/config/a2a/security-scheme/completion.ts new file mode 100644 index 0000000000..bb015c666f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/completion.ts @@ -0,0 +1,30 @@ +import { + ApidomCompletionItem, + CompletionFormat, + CompletionType, +} from '../../../apidom-language-types.ts'; +import { A2A1 } from '../target-specs.ts'; + +const schemeWrapperField = (label: string, docs: string): ApidomCompletionItem => ({ + label, + insertText: label, + kind: 14, + format: CompletionFormat.OBJECT, + type: CompletionType.PROPERTY, + insertTextFormat: 2, + documentation: { kind: 'markdown', value: docs }, + targetSpecs: A2A1, +}); + +const completion: ApidomCompletionItem[] = [ + schemeWrapperField('apiKeySecurityScheme', 'API key authentication scheme.'), + schemeWrapperField( + 'httpAuthSecurityScheme', + 'HTTP authentication scheme (Basic, Bearer, etc., per RFC 7235).', + ), + schemeWrapperField('mtlsSecurityScheme', 'Mutual TLS authentication scheme.'), + schemeWrapperField('oauth2SecurityScheme', 'OAuth 2.0 authentication scheme.'), + schemeWrapperField('openIdConnectSecurityScheme', 'OpenID Connect authentication scheme.'), +]; + +export default completion; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/documentation.ts b/packages/apidom-ls/src/config/a2a/security-scheme/documentation.ts new file mode 100644 index 0000000000..aafc1b75af --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/documentation.ts @@ -0,0 +1,36 @@ +import { A2A1 } from '../target-specs.ts'; + +/** + * Hover documentation for A2A v1 SecurityScheme fields. SecurityScheme is a + * discriminated union — exactly one of the subtype fields must be set. + * See [SecurityScheme](https://a2a-protocol.org/latest/specification/#451-securityscheme). + */ +const documentation = [ + { + target: 'apiKeySecurityScheme', + docs: '[API Key Security Scheme](https://a2a-protocol.org/latest/specification/#452-apikeysecurityscheme) — API key-based authentication. Set this OR exactly one other subtype.', + targetSpecs: A2A1, + }, + { + target: 'httpAuthSecurityScheme', + docs: '[HTTP Auth Security Scheme](https://a2a-protocol.org/latest/specification/#453-httpauthsecurityscheme) — HTTP authentication (Basic, Bearer, etc.). Set this OR exactly one other subtype.', + targetSpecs: A2A1, + }, + { + target: 'oauth2SecurityScheme', + docs: '[OAuth2 Security Scheme](https://a2a-protocol.org/latest/specification/#454-oauth2securityscheme) — OAuth 2.0 authentication. Set this OR exactly one other subtype.', + targetSpecs: A2A1, + }, + { + target: 'openIdConnectSecurityScheme', + docs: '[OpenID Connect Security Scheme](https://a2a-protocol.org/latest/specification/#455-openidconnectsecurityscheme) — OpenID Connect authentication. Set this OR exactly one other subtype.', + targetSpecs: A2A1, + }, + { + target: 'mtlsSecurityScheme', + docs: '[Mutual TLS Security Scheme](https://a2a-protocol.org/latest/specification/#456-mutualtlssecurityscheme) — mutual TLS authentication. Set this OR exactly one other subtype.', + targetSpecs: A2A1, + }, +]; + +export default documentation; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/allowed-fields.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/allowed-fields.ts new file mode 100644 index 0000000000..a8743c8140 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/allowed-fields.ts @@ -0,0 +1,26 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const allowedFieldsLint: LinterMeta = { + code: ApilintCodes.NOT_ALLOWED_FIELDS, + source: 'apilint', + message: 'Object includes not allowed fields', + severity: DiagnosticSeverity.Error, + linterFunction: 'allowedFields', + linterParams: [ + [ + 'apiKeySecurityScheme', + 'httpAuthSecurityScheme', + 'mtlsSecurityScheme', + 'oauth2SecurityScheme', + 'openIdConnectSecurityScheme', + ], + ], + marker: 'key', + targetSpecs: A2A1, +}; + +export default allowedFieldsLint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/api-key-security-scheme--type.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/api-key-security-scheme--type.ts new file mode 100644 index 0000000000..27ebf2fe5c --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/api-key-security-scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_SECURITY_SCHEME_FIELD_API_KEY_TYPE, + source: 'apilint', + message: "'apiKeySecurityScheme' must be an API Key Security Scheme Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['apiKeySecurityScheme'], + marker: 'value', + target: 'apiKeySecurityScheme', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/http-auth-security-scheme--type.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/http-auth-security-scheme--type.ts new file mode 100644 index 0000000000..b144c7f4d0 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/http-auth-security-scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_SECURITY_SCHEME_FIELD_HTTP_AUTH_TYPE, + source: 'apilint', + message: "'httpAuthSecurityScheme' must be an HTTP Auth Security Scheme Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['httpAuthSecurityScheme'], + marker: 'value', + target: 'httpAuthSecurityScheme', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/index.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/index.ts new file mode 100644 index 0000000000..ae758fcc4f --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/index.ts @@ -0,0 +1,17 @@ +import allowedFieldsLint from './allowed-fields.ts'; +import apiKeySecuritySchemeLint from './api-key-security-scheme--type.ts'; +import httpAuthSecuritySchemeLint from './http-auth-security-scheme--type.ts'; +import mtlsSecuritySchemeLint from './mtls-security-scheme--type.ts'; +import oauth2SecuritySchemeLint from './oauth2-security-scheme--type.ts'; +import openIdConnectSecuritySchemeLint from './open-id-connect-security-scheme--type.ts'; + +const lints = [ + allowedFieldsLint, + apiKeySecuritySchemeLint, + httpAuthSecuritySchemeLint, + mtlsSecuritySchemeLint, + oauth2SecuritySchemeLint, + openIdConnectSecuritySchemeLint, +]; + +export default lints; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/mtls-security-scheme--type.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/mtls-security-scheme--type.ts new file mode 100644 index 0000000000..5af77666e2 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/mtls-security-scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_SECURITY_SCHEME_FIELD_MTLS_TYPE, + source: 'apilint', + message: "'mtlsSecurityScheme' must be a Mutual TLS Security Scheme Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['mutualTlsSecurityScheme'], + marker: 'value', + target: 'mtlsSecurityScheme', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/oauth2-security-scheme--type.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/oauth2-security-scheme--type.ts new file mode 100644 index 0000000000..640a10b9e6 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/oauth2-security-scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_SECURITY_SCHEME_FIELD_OAUTH2_TYPE, + source: 'apilint', + message: "'oauth2SecurityScheme' must be an OAuth 2.0 Security Scheme Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['oauth2SecurityScheme'], + marker: 'value', + target: 'oauth2SecurityScheme', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/lint/open-id-connect-security-scheme--type.ts b/packages/apidom-ls/src/config/a2a/security-scheme/lint/open-id-connect-security-scheme--type.ts new file mode 100644 index 0000000000..79b64dc221 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/lint/open-id-connect-security-scheme--type.ts @@ -0,0 +1,19 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { A2A1 } from '../../target-specs.ts'; + +const lint: LinterMeta = { + code: ApilintCodes.A2A1_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_TYPE, + source: 'apilint', + message: "'openIdConnectSecurityScheme' must be an OpenID Connect Security Scheme Object", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintElementOrClass', + linterParams: ['openIdConnectSecurityScheme'], + marker: 'value', + target: 'openIdConnectSecurityScheme', + targetSpecs: A2A1, +}; + +export default lint; diff --git a/packages/apidom-ls/src/config/a2a/security-scheme/meta.ts b/packages/apidom-ls/src/config/a2a/security-scheme/meta.ts new file mode 100644 index 0000000000..6e1bbd7f60 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/security-scheme/meta.ts @@ -0,0 +1,12 @@ +import { FormatMeta } from '../../../apidom-language-types.ts'; +import lint from './lint/index.ts'; +import completion from './completion.ts'; +import documentation from './documentation.ts'; + +const meta: FormatMeta = { + lint, + completion, + documentation, +}; + +export default meta; diff --git a/packages/apidom-ls/src/config/a2a/target-specs.ts b/packages/apidom-ls/src/config/a2a/target-specs.ts new file mode 100644 index 0000000000..95b35b8aa4 --- /dev/null +++ b/packages/apidom-ls/src/config/a2a/target-specs.ts @@ -0,0 +1,4 @@ +export const A2A100 = [{ namespace: 'a2a', version: '1.0.0' }]; +export const A2A101 = [{ namespace: 'a2a', version: '1.0.1' }]; + +export const A2A1 = [...A2A100, ...A2A101]; diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index d0e8cafbf9..31d7cda3f5 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -1435,6 +1435,145 @@ enum ApilintCodes { JSON_SCHEMA_2020_12_KEYWORD_$SCHEMA_FORMAT_URI = 8030200, JSON_SCHEMA_2020_12_KEYWORD_$REF_FORMAT_URI = 8030300, JSON_SCHEMA_2020_12_KEYWORD_$COMMENT_TYPE = 8030400, + + A2A1 = 9000000, + + A2A1_AGENT_CARD = 9010000, + A2A1_AGENT_CARD_FIELD_NAME_TYPE = 9010100, + A2A1_AGENT_CARD_FIELD_DESCRIPTION_TYPE = 9010200, + A2A1_AGENT_CARD_FIELD_VERSION_TYPE = 9010400, + A2A1_AGENT_CARD_FIELD_ICON_URL_TYPE = 9010500, + A2A1_AGENT_CARD_FIELD_DOCUMENTATION_URL_TYPE = 9010600, + A2A1_AGENT_CARD_FIELD_PROVIDER_TYPE = 9010700, + A2A1_AGENT_CARD_FIELD_CAPABILITIES_TYPE = 9010800, + A2A1_AGENT_CARD_FIELD_DEFAULT_INPUT_MODES_TYPE = 9010900, + A2A1_AGENT_CARD_FIELD_DEFAULT_OUTPUT_MODES_TYPE = 9011000, + A2A1_AGENT_CARD_FIELD_SUPPORTED_INTERFACES_TYPE = 9011100, + A2A1_AGENT_CARD_FIELD_SKILLS_TYPE = 9011200, + A2A1_AGENT_CARD_FIELD_SECURITY_SCHEMES_TYPE = 9011300, + A2A1_AGENT_CARD_FIELD_SECURITY_REQUIREMENTS_TYPE = 9011400, + A2A1_AGENT_CARD_FIELD_SIGNATURES_TYPE = 9011500, + A2A1_AGENT_CARD_FIELD_NAME_REQUIRED = 9011600, + A2A1_AGENT_CARD_FIELD_DESCRIPTION_REQUIRED = 9011700, + A2A1_AGENT_CARD_FIELD_SUPPORTED_INTERFACES_REQUIRED = 9011800, + A2A1_AGENT_CARD_FIELD_VERSION_REQUIRED = 9011900, + A2A1_AGENT_CARD_FIELD_CAPABILITIES_REQUIRED = 9012000, + A2A1_AGENT_CARD_FIELD_DEFAULT_INPUT_MODES_REQUIRED = 9012100, + A2A1_AGENT_CARD_FIELD_DEFAULT_OUTPUT_MODES_REQUIRED = 9012200, + A2A1_AGENT_CARD_FIELD_SKILLS_REQUIRED = 9012300, + + A2A1_AGENT_CAPABILITIES = 9020000, + A2A1_AGENT_CAPABILITIES_FIELD_STREAMING_TYPE = 9020100, + A2A1_AGENT_CAPABILITIES_FIELD_PUSH_NOTIFICATIONS_TYPE = 9020200, + A2A1_AGENT_CAPABILITIES_FIELD_EXTENDED_AGENT_CARD_TYPE = 9020300, + A2A1_AGENT_CAPABILITIES_FIELD_EXTENSIONS_TYPE = 9020400, + + A2A1_AGENT_SKILL = 9030000, + A2A1_AGENT_SKILL_FIELD_ID_TYPE = 9030100, + A2A1_AGENT_SKILL_FIELD_NAME_TYPE = 9030200, + A2A1_AGENT_SKILL_FIELD_DESCRIPTION_TYPE = 9030300, + A2A1_AGENT_SKILL_FIELD_TAGS_TYPE = 9030400, + A2A1_AGENT_SKILL_FIELD_EXAMPLES_TYPE = 9030500, + A2A1_AGENT_SKILL_FIELD_INPUT_MODES_TYPE = 9030600, + A2A1_AGENT_SKILL_FIELD_OUTPUT_MODES_TYPE = 9030700, + A2A1_AGENT_SKILL_FIELD_SECURITY_REQUIREMENTS_TYPE = 9030800, + A2A1_AGENT_SKILL_FIELD_ID_REQUIRED = 9030900, + A2A1_AGENT_SKILL_FIELD_NAME_REQUIRED = 9031000, + A2A1_AGENT_SKILL_FIELD_DESCRIPTION_REQUIRED = 9031100, + A2A1_AGENT_SKILL_FIELD_TAGS_REQUIRED = 9031200, + + A2A1_AGENT_PROVIDER = 9040000, + A2A1_AGENT_PROVIDER_FIELD_ORGANIZATION_TYPE = 9040100, + A2A1_AGENT_PROVIDER_FIELD_URL_TYPE = 9040200, + A2A1_AGENT_PROVIDER_FIELD_ORGANIZATION_REQUIRED = 9040300, + A2A1_AGENT_PROVIDER_FIELD_URL_REQUIRED = 9040400, + + A2A1_AGENT_INTERFACE = 9050000, + A2A1_AGENT_INTERFACE_FIELD_URL_TYPE = 9050100, + A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_BINDING_TYPE = 9050200, + A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_VERSION_TYPE = 9050300, + A2A1_AGENT_INTERFACE_FIELD_TENANT_TYPE = 9050400, + A2A1_AGENT_INTERFACE_FIELD_URL_REQUIRED = 9050500, + A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_BINDING_REQUIRED = 9050600, + A2A1_AGENT_INTERFACE_FIELD_PROTOCOL_VERSION_REQUIRED = 9050700, + + A2A1_SECURITY_SCHEME = 9060000, + A2A1_SECURITY_SCHEME_FIELD_API_KEY_TYPE = 9060100, + A2A1_SECURITY_SCHEME_FIELD_HTTP_AUTH_TYPE = 9060200, + A2A1_SECURITY_SCHEME_FIELD_MTLS_TYPE = 9060300, + A2A1_SECURITY_SCHEME_FIELD_OAUTH2_TYPE = 9060400, + A2A1_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_TYPE = 9060500, + + A2A1_AGENT_CARD_SIGNATURE = 9070000, + A2A1_AGENT_CARD_SIGNATURE_FIELD_PROTECTED_TYPE = 9070100, + A2A1_AGENT_CARD_SIGNATURE_FIELD_SIGNATURE_TYPE = 9070200, + A2A1_AGENT_CARD_SIGNATURE_FIELD_HEADER_TYPE = 9070300, + A2A1_AGENT_CARD_SIGNATURE_FIELD_PROTECTED_REQUIRED = 9070400, + A2A1_AGENT_CARD_SIGNATURE_FIELD_SIGNATURE_REQUIRED = 9070500, + + A2A1_OAUTH_FLOWS = 9080000, + A2A1_OAUTH_FLOWS_FIELD_AUTHORIZATION_CODE_TYPE = 9080100, + A2A1_OAUTH_FLOWS_FIELD_CLIENT_CREDENTIALS_TYPE = 9080200, + A2A1_OAUTH_FLOWS_FIELD_DEVICE_CODE_TYPE = 9080300, + + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW = 9090000, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_AUTHORIZATION_URL_TYPE = 9090100, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE = 9090200, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE = 9090300, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_PKCE_REQUIRED_TYPE = 9090400, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_SCOPES_TYPE = 9090500, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_AUTHORIZATION_URL_REQUIRED = 9090600, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED = 9090700, + A2A1_AUTHORIZATION_CODE_OAUTH_FLOW_FIELD_SCOPES_REQUIRED = 9090800, + + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW = 9100000, + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE = 9100100, + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE = 9100200, + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_SCOPES_TYPE = 9100300, + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED = 9100400, + A2A1_CLIENT_CREDENTIALS_OAUTH_FLOW_FIELD_SCOPES_REQUIRED = 9100500, + + A2A1_DEVICE_CODE_OAUTH_FLOW = 9110000, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_DEVICE_AUTHORIZATION_URL_TYPE = 9110100, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_TYPE = 9110200, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_REFRESH_URL_TYPE = 9110300, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_SCOPES_TYPE = 9110400, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_DEVICE_AUTHORIZATION_URL_REQUIRED = 9110500, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_TOKEN_URL_REQUIRED = 9110600, + A2A1_DEVICE_CODE_OAUTH_FLOW_FIELD_SCOPES_REQUIRED = 9110700, + + A2A1_AGENT_EXTENSION = 9120000, + A2A1_AGENT_EXTENSION_FIELD_URI_TYPE = 9120100, + A2A1_AGENT_EXTENSION_FIELD_DESCRIPTION_TYPE = 9120200, + A2A1_AGENT_EXTENSION_FIELD_REQUIRED_TYPE = 9120300, + A2A1_AGENT_EXTENSION_FIELD_PARAMS_TYPE = 9120400, + + A2A1_MUTUAL_TLS_SECURITY_SCHEME = 9130000, + A2A1_MUTUAL_TLS_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE = 9130100, + + A2A1_API_KEY_SECURITY_SCHEME = 9140000, + A2A1_API_KEY_SECURITY_SCHEME_FIELD_LOCATION_REQUIRED = 9140100, + A2A1_API_KEY_SECURITY_SCHEME_FIELD_NAME_REQUIRED = 9140200, + A2A1_API_KEY_SECURITY_SCHEME_FIELD_NAME_TYPE = 9140300, + A2A1_API_KEY_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE = 9140400, + A2A1_API_KEY_SECURITY_SCHEME_FIELD_LOCATION_VALUE = 9140500, + + A2A1_HTTP_AUTH_SECURITY_SCHEME = 9150000, + A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_SCHEME_REQUIRED = 9150100, + A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_SCHEME_TYPE = 9150200, + A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE = 9150300, + A2A1_HTTP_AUTH_SECURITY_SCHEME_FIELD_BEARER_FORMAT_TYPE = 9150400, + + A2A1_OAUTH2_SECURITY_SCHEME = 9160000, + A2A1_OAUTH2_SECURITY_SCHEME_FIELD_FLOWS_REQUIRED = 9160100, + A2A1_OAUTH2_SECURITY_SCHEME_FIELD_FLOWS_TYPE = 9160200, + A2A1_OAUTH2_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE = 9160300, + A2A1_OAUTH2_SECURITY_SCHEME_FIELD_OAUTH2_METADATA_URL_TYPE = 9160400, + + A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME = 9170000, + A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_URL_REQUIRED = 9170100, + A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_OPEN_ID_CONNECT_URL_TYPE = 9170200, + A2A1_OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_DESCRIPTION_TYPE = 9170300, } export default ApilintCodes; diff --git a/packages/apidom-ls/src/config/config.ts b/packages/apidom-ls/src/config/config.ts index 2da7cfbcee..4a5921036a 100644 --- a/packages/apidom-ls/src/config/config.ts +++ b/packages/apidom-ls/src/config/config.ts @@ -1,6 +1,7 @@ import configAsyncAPI from './asyncapi/config.ts'; import configOpenAPI from './openapi/config.ts'; import configADS from './ads/config.ts'; +import configA2A from './a2a/config.ts'; import configJSONSchema202012 from './json-schema/2020-12/config.ts'; import asyncapiReferenceMeta from './asyncapi/reference/meta.ts'; import openapiReferenceMeta from './openapi/reference/meta.ts'; @@ -18,6 +19,7 @@ export function config(): Metadata { openapi: configOpenAPI, asyncapi: configAsyncAPI, ads: configADS, + a2a: configA2A, 'json-schema-2020-12': configJSONSchema202012, }, rules: { diff --git a/packages/apidom-ls/src/parser-factory.ts b/packages/apidom-ls/src/parser-factory.ts index 53fcdd172d..2160b29a81 100644 --- a/packages/apidom-ls/src/parser-factory.ts +++ b/packages/apidom-ls/src/parser-factory.ts @@ -10,6 +10,8 @@ import * as asyncapi2AdapterJson from '@swagger-api/apidom-parser-adapter-asynca import * as asyncapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-2'; import * as asyncapi3AdapterJson from '@swagger-api/apidom-parser-adapter-asyncapi-json-3'; import * as asyncapi3AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-3'; +import * as a2a1AdapterJson from '@swagger-api/apidom-parser-adapter-a2a-json-1'; +import * as a2a1AdapterYaml from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; import * as adsAdapterJson from '@swagger-api/apidom-parser-adapter-api-design-systems-json'; import * as adsAdapterYaml from '@swagger-api/apidom-parser-adapter-api-design-systems-yaml'; import * as adapterJson from '@swagger-api/apidom-parser-adapter-json'; @@ -20,6 +22,7 @@ import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElemen import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_0 } from '@swagger-api/apidom-ns-openapi-3-0'; import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_1 } from '@swagger-api/apidom-ns-openapi-3-1'; import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_2 } from '@swagger-api/apidom-ns-openapi-3-2'; +import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementA2A1 } from '@swagger-api/apidom-ns-a2a-1'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { ParseResultElement } from '@swagger-api/apidom-core'; @@ -219,6 +222,27 @@ export async function parse( }; result = await openapi3_2AdapterYaml.parse(text, options); + } else if (contentLanguage.namespace === 'a2a' && contentLanguage.format === 'JSON') { + const options: Record = { + sourceMap: true, + refractorOpts: { + plugins: [...(refractorPlugins?.['a2a-1'] || [])], + }, + }; + + result = await a2a1AdapterJson.parse(text, options); + } else if (contentLanguage.namespace === 'a2a' && contentLanguage.format === 'YAML') { + const options: Record = { + sourceMap: true, + refractorOpts: { + plugins: [ + registerPlugins && refractorPluginReplaceEmptyElementA2A1(), + ...(refractorPlugins?.['a2a-1'] || []), + ].filter(Boolean), + }, + }; + + result = await a2a1AdapterYaml.parse(text, options); } else if (contentLanguage.namespace === 'ads' && contentLanguage.format === 'JSON') { result = await adsAdapterJson.parse(text, { sourceMap: true }); } else if (contentLanguage.namespace === 'ads' && contentLanguage.format === 'YAML') { diff --git a/packages/apidom-ls/src/utils/utils.ts b/packages/apidom-ls/src/utils/utils.ts index b0bf5ac11b..74cb9c6b10 100644 --- a/packages/apidom-ls/src/utils/utils.ts +++ b/packages/apidom-ls/src/utils/utils.ts @@ -10,6 +10,8 @@ import * as asyncapi2AdapterJson from '@swagger-api/apidom-parser-adapter-asynca import * as asyncapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-2'; import * as asyncapi3AdapterJson from '@swagger-api/apidom-parser-adapter-asyncapi-json-3'; import * as asyncapi3AdapterYaml from '@swagger-api/apidom-parser-adapter-asyncapi-yaml-3'; +import * as a2a1AdapterJson from '@swagger-api/apidom-parser-adapter-a2a-json-1'; +import * as a2a1AdapterYaml from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; import * as adsAdapterJson from '@swagger-api/apidom-parser-adapter-api-design-systems-json'; import * as adsAdapterYaml from '@swagger-api/apidom-parser-adapter-api-design-systems-yaml'; import * as adapterJson from '@swagger-api/apidom-parser-adapter-json'; @@ -974,6 +976,31 @@ export async function findNamespace( }; } + if (await a2a1AdapterJson.detect(text)) { + // A2A AgentCard documents have no version discriminator field, so we + // pin the LS namespace identification to A2A v1 (matches the registered + // media types in the adapter). + const version = '1.0.1'; + + return { + namespace: 'a2a', + version, + format: 'JSON', + mediaType: a2a1AdapterJson.mediaTypes.findBy(version, 'json'), + }; + } + + if (await a2a1AdapterYaml.detect(text)) { + const version = '1.0.1'; + + return { + namespace: 'a2a', + version, + format: 'YAML', + mediaType: a2a1AdapterYaml.mediaTypes.findBy(version, 'yaml'), + }; + } + if (await adsAdapterJson.detect(text)) { const adsJsonMatch = text.match(adsAdapterJson.detectionRegExp)!; const groups = adsJsonMatch.groups!; diff --git a/packages/apidom-ls/test/a2a.ts b/packages/apidom-ls/test/a2a.ts new file mode 100644 index 0000000000..4830d232ad --- /dev/null +++ b/packages/apidom-ls/test/a2a.ts @@ -0,0 +1,400 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { assert } from 'chai'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'; +import { fileURLToPath } from 'node:url'; + +import getLanguageService from '../src/apidom-language-service.ts'; +import { + LanguageService, + LanguageServiceContext, + ValidationContext, +} from '../src/apidom-language-types.ts'; +import { metadata } from './metadata.ts'; +import { logPerformance, logLevel } from './test-utils.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const agentCardValid = fs + .readFileSync(path.join(__dirname, 'fixtures', 'a2a', 'agent-card-valid.json')) + .toString(); + +const agentCardMissingRequired = fs + .readFileSync(path.join(__dirname, 'fixtures', 'a2a', 'agent-card-missing-required.json')) + .toString(); + +const agentCardOAuthValid = fs + .readFileSync(path.join(__dirname, 'fixtures', 'a2a', 'agent-card-oauth-valid.json')) + .toString(); + +const agentCardOAuthMissingScopes = fs + .readFileSync(path.join(__dirname, 'fixtures', 'a2a', 'agent-card-oauth-missing-scopes.json')) + .toString(); + +const agentCardClientCredentialsMissingScopes = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'a2a', 'agent-card-client-credentials-missing-scopes.json'), + ) + .toString(); + +const agentCardDeviceCodeMissingScopes = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'a2a', 'agent-card-device-code-missing-scopes.json'), + ) + .toString(); + +describe('apidom-ls-a2a', function () { + const context: LanguageServiceContext = { + metadata: metadata(), + performanceLogs: logPerformance, + logLevel, + // A2A AgentCard documents carry no version discriminator field, so the + // spec version used for lint-rule targeting is pinned to A2A v1. + defaultContentLanguage: { + namespace: 'a2a', + version: '1.0.1', + format: 'JSON', + mediaType: 'application/vnd.a2a+json;version=1.0.1', + }, + }; + + const languageService: LanguageService = getLanguageService(context); + + after(function () { + languageService.terminate(); + }); + + it('validates a well-formed AgentCard with no diagnostics', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc: TextDocument = TextDocument.create( + 'foo://bar/agent-card.json', + 'json', + 0, + agentCardValid, + ); + + const result = await languageService.doValidation(doc, validationContext); + assert.deepEqual(result, [] as Diagnostic[]); + }); + + it('reports missing required fields on AgentCard and nested AgentSkill', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc: TextDocument = TextDocument.create( + 'foo://bar/agent-card-missing.json', + 'json', + 0, + agentCardMissingRequired, + ); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + range: { start: { line: 0, character: 0 }, end: { line: 1, character: 3 } }, + message: "should always have a 'description' field", + severity: 1, + code: 9011700, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'description' field", + action: 'addChild', + snippetYaml: "description: ''\n", + snippetJson: '"description": "",\n', + }, + ], + }, + }, + { + range: { start: { line: 0, character: 0 }, end: { line: 1, character: 3 } }, + message: "should always have a 'supportedInterfaces' field", + severity: 1, + code: 9011800, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'supportedInterfaces' field", + action: 'addChild', + snippetYaml: 'supportedInterfaces:\n - \n', + snippetJson: '"supportedInterfaces": [],\n', + }, + ], + }, + }, + { + range: { start: { line: 0, character: 0 }, end: { line: 1, character: 3 } }, + message: "should always have a 'version' field", + severity: 1, + code: 9011900, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'version' field", + action: 'addChild', + snippetYaml: "version: ''\n", + snippetJson: '"version": "",\n', + }, + ], + }, + }, + { + range: { start: { line: 0, character: 0 }, end: { line: 1, character: 3 } }, + message: "should always have a 'defaultInputModes' field", + severity: 1, + code: 9012100, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'defaultInputModes' field", + action: 'addChild', + snippetYaml: 'defaultInputModes:\n - \n', + snippetJson: '"defaultInputModes": [],\n', + }, + ], + }, + }, + { + range: { start: { line: 0, character: 0 }, end: { line: 1, character: 3 } }, + message: "should always have a 'defaultOutputModes' field", + severity: 1, + code: 9012200, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'defaultOutputModes' field", + action: 'addChild', + snippetYaml: 'defaultOutputModes:\n - \n', + snippetJson: '"defaultOutputModes": [],\n', + }, + ], + }, + }, + { + range: { start: { line: 1, character: 10 }, end: { line: 1, character: 13 } }, + message: "'name' must be a string", + severity: 1, + code: 9010100, + source: 'apilint', + }, + { + range: { start: { line: 6, character: 4 }, end: { line: 8, character: 5 } }, + message: "should always have an 'id' field", + severity: 1, + code: 9030900, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'id' field", + action: 'addChild', + snippetYaml: "id: ''\n", + snippetJson: '"id": "",\n', + }, + ], + }, + }, + { + range: { start: { line: 6, character: 4 }, end: { line: 8, character: 5 } }, + message: "should always have a 'name' field", + severity: 1, + code: 9031000, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'name' field", + action: 'addChild', + snippetYaml: "name: ''\n", + snippetJson: '"name": "",\n', + }, + ], + }, + }, + { + range: { start: { line: 6, character: 4 }, end: { line: 8, character: 5 } }, + message: "should always have a 'description' field", + severity: 1, + code: 9031100, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'description' field", + action: 'addChild', + snippetYaml: "description: ''\n", + snippetJson: '"description": "",\n', + }, + ], + }, + }, + { + range: { start: { line: 6, character: 4 }, end: { line: 8, character: 5 } }, + message: "should always have a 'tags' field", + severity: 1, + code: 9031200, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'tags' field", + action: 'addChild', + snippetYaml: 'tags:\n - \n', + snippetJson: '"tags": [],\n', + }, + ], + }, + }, + ]; + assert.deepEqual(result, expected); + }); + + it('validates an AgentCard with oauth2 authorization code flow and scopes with no diagnostics', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc = TextDocument.create('foo://bar/oauth-valid.json', 'json', 0, agentCardOAuthValid); + const result = await languageService.doValidation(doc, validationContext); + assert.deepEqual(result, [] as Diagnostic[]); + }); + + it('reports missing scopes on AuthorizationCodeOAuthFlow', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc = TextDocument.create( + 'foo://bar/oauth-missing-scopes.json', + 'json', + 0, + agentCardOAuthMissingScopes, + ); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + range: { start: { line: 26, character: 10 }, end: { line: 26, character: 29 } }, + message: "should always have a 'scopes' field", + severity: 1, + code: 9090800, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, + }, + ]; + assert.deepEqual(result, expected); + }); + + it('reports missing scopes on ClientCredentialsOAuthFlow', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc = TextDocument.create( + 'foo://bar/cc-missing-scopes.json', + 'json', + 0, + agentCardClientCredentialsMissingScopes, + ); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + range: { start: { line: 26, character: 10 }, end: { line: 26, character: 29 } }, + message: "should always have a 'scopes' field", + severity: 1, + code: 9100500, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, + }, + ]; + assert.deepEqual(result, expected); + }); + + it('reports missing scopes on DeviceCodeOAuthFlow', async function () { + this.timeout(10000); + + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const doc = TextDocument.create( + 'foo://bar/dc-missing-scopes.json', + 'json', + 0, + agentCardDeviceCodeMissingScopes, + ); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + range: { start: { line: 26, character: 10 }, end: { line: 26, character: 22 } }, + message: "should always have a 'scopes' field", + severity: 1, + code: 9110700, + source: 'apilint', + data: { + quickFix: [ + { + message: "add 'scopes' field", + action: 'addChild', + snippetYaml: 'scopes: \n \n', + snippetJson: '"scopes": {\n \n },\n', + }, + ], + }, + }, + ]; + assert.deepEqual(result, expected); + }); +}); diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-client-credentials-missing-scopes.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-client-credentials-missing-scopes.json new file mode 100644 index 0000000000..167df078d5 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-client-credentials-missing-scopes.json @@ -0,0 +1,35 @@ +{ + "name": "OAuth Agent", + "description": "Agent with OAuth2 client credentials flow missing scopes.", + "version": "1.0.0", + "supportedInterfaces": [ + { + "url": "https://agent.example.com/a2a/v1", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + } + ], + "capabilities": { "streaming": false }, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [ + { + "id": "skill1", + "name": "Skill One", + "description": "A skill.", + "tags": ["test"] + } + ], + "securitySchemes": { + "oauth": { + "oauth2SecurityScheme": { + "flows": { + "clientCredentials": { + "tokenUrl": "https://example.com/token" + } + } + } + } + }, + "securityRequirements": [{ "schemes": { "oauth": [] } }] +} diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-device-code-missing-scopes.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-device-code-missing-scopes.json new file mode 100644 index 0000000000..8a9a7d0880 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-device-code-missing-scopes.json @@ -0,0 +1,36 @@ +{ + "name": "OAuth Agent", + "description": "Agent with OAuth2 device code flow missing scopes.", + "version": "1.0.0", + "supportedInterfaces": [ + { + "url": "https://agent.example.com/a2a/v1", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + } + ], + "capabilities": { "streaming": false }, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [ + { + "id": "skill1", + "name": "Skill One", + "description": "A skill.", + "tags": ["test"] + } + ], + "securitySchemes": { + "oauth": { + "oauth2SecurityScheme": { + "flows": { + "deviceCode": { + "deviceAuthorizationUrl": "https://example.com/device", + "tokenUrl": "https://example.com/token" + } + } + } + } + }, + "securityRequirements": [{ "schemes": { "oauth": [] } }] +} diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-missing-required.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-missing-required.json new file mode 100644 index 0000000000..4460a73e14 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-missing-required.json @@ -0,0 +1,11 @@ +{ + "name": 123, + "capabilities": { + "streaming": true + }, + "skills": [ + { + "examples": ["do a thing"] + } + ] +} diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-missing-scopes.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-missing-scopes.json new file mode 100644 index 0000000000..d4ecdad907 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-missing-scopes.json @@ -0,0 +1,36 @@ +{ + "name": "OAuth Agent", + "description": "Agent with OAuth2 authorization code flow missing scopes.", + "version": "1.0.0", + "supportedInterfaces": [ + { + "url": "https://agent.example.com/a2a/v1", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + } + ], + "capabilities": { "streaming": false }, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [ + { + "id": "skill1", + "name": "Skill One", + "description": "A skill.", + "tags": ["test"] + } + ], + "securitySchemes": { + "oauth": { + "oauth2SecurityScheme": { + "flows": { + "authorizationCode": { + "authorizationUrl": "https://example.com/auth", + "tokenUrl": "https://example.com/token" + } + } + } + } + }, + "securityRequirements": [{ "schemes": { "oauth": [] } }] +} diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-valid.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-valid.json new file mode 100644 index 0000000000..7345690336 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-oauth-valid.json @@ -0,0 +1,37 @@ +{ + "name": "OAuth Agent", + "description": "Agent with OAuth2 authorization code flow.", + "version": "1.0.0", + "supportedInterfaces": [ + { + "url": "https://agent.example.com/a2a/v1", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + } + ], + "capabilities": { "streaming": false }, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [ + { + "id": "skill1", + "name": "Skill One", + "description": "A skill.", + "tags": ["test"] + } + ], + "securitySchemes": { + "oauth": { + "oauth2SecurityScheme": { + "flows": { + "authorizationCode": { + "authorizationUrl": "https://example.com/auth", + "tokenUrl": "https://example.com/token", + "scopes": { "read": "Read access" } + } + } + } + } + }, + "securityRequirements": [{ "schemes": { "oauth": ["read"] } }] +} diff --git a/packages/apidom-ls/test/fixtures/a2a/agent-card-valid.json b/packages/apidom-ls/test/fixtures/a2a/agent-card-valid.json new file mode 100644 index 0000000000..4536e6a75e --- /dev/null +++ b/packages/apidom-ls/test/fixtures/a2a/agent-card-valid.json @@ -0,0 +1,50 @@ +{ + "name": "Planner Agent", + "description": "Breaks down a request into actionable tasks.", + "version": "1.0.0", + "iconUrl": "https://agent.example.com/icon.png", + "documentationUrl": "https://agent.example.com/docs", + "provider": { + "organization": "Example Co.", + "url": "https://example.com/" + }, + "capabilities": { + "streaming": true, + "pushNotifications": true, + "extendedAgentCard": false + }, + "supportedInterfaces": [ + { + "url": "https://agent.example.com/a2a/v1", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + } + ], + "defaultInputModes": ["text", "text/plain"], + "defaultOutputModes": ["text", "text/plain"], + "skills": [ + { + "id": "planner", + "name": "Task Planner", + "description": "Plans tasks based on user goals.", + "tags": ["planner"], + "examples": ["Plan a business trip"] + } + ], + "securitySchemes": { + "bearer": { + "httpAuthSecurityScheme": { + "description": "Bearer token authentication.", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "securityRequirements": [ + { + "schemes": { + "bearer": [] + } + } + ] +} diff --git a/packages/apidom-ns-a2a-1/.eslintignore b/packages/apidom-ns-a2a-1/.eslintignore new file mode 100644 index 0000000000..5637a4f9c6 --- /dev/null +++ b/packages/apidom-ns-a2a-1/.eslintignore @@ -0,0 +1,9 @@ +/**/*.js +/**/*.mjs +/**/*.cjs +/dist +/config +/types +/.eslintrc.js +/.nyc_output +/node_modules diff --git a/packages/apidom-ns-a2a-1/.gitignore b/packages/apidom-ns-a2a-1/.gitignore new file mode 100644 index 0000000000..9026be80f3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/.gitignore @@ -0,0 +1,7 @@ +/src/**/*.mjs +/src/**/*.cjs +/test/**/*.mjs +/dist +/types +/NOTICE +/swagger-api-apidom-ns-a2a-1-*.tgz diff --git a/packages/apidom-ns-a2a-1/.mocharc.json b/packages/apidom-ns-a2a-1/.mocharc.json new file mode 100644 index 0000000000..38ee8de7ec --- /dev/null +++ b/packages/apidom-ns-a2a-1/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extensions": ["ts"], + "loader": "ts-node/esm", + "recursive": true, + "spec": "test/**/*.ts", + "file": ["test/mocha-bootstrap.ts"] +} diff --git a/packages/apidom-ns-a2a-1/.npmrc b/packages/apidom-ns-a2a-1/.npmrc new file mode 100644 index 0000000000..4b82d2e7bb --- /dev/null +++ b/packages/apidom-ns-a2a-1/.npmrc @@ -0,0 +1,2 @@ +save-prefix="=" +save=false diff --git a/packages/apidom-ns-a2a-1/CHANGELOG.md b/packages/apidom-ns-a2a-1/CHANGELOG.md new file mode 100644 index 0000000000..7dd6dc3ad1 --- /dev/null +++ b/packages/apidom-ns-a2a-1/CHANGELOG.md @@ -0,0 +1,10 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 1.11.1 (2026-05-27) + +### Features + +- **a2a:** initial namespace package for A2A (Agent-to-Agent) Protocol v1.0. Models the AgentCard document with 21 element classes (AgentCard, AgentCapabilities, AgentExtension, AgentProvider, AgentInterface, AgentSkill, AgentCardSignature, SecurityRequirement, SecurityScheme + 5 concrete subtypes, OAuthFlows + 5 flow subtypes, StringList). diff --git a/packages/apidom-ns-a2a-1/README.md b/packages/apidom-ns-a2a-1/README.md new file mode 100644 index 0000000000..ddb3461528 --- /dev/null +++ b/packages/apidom-ns-a2a-1/README.md @@ -0,0 +1,60 @@ +# @swagger-api/apidom-ns-a2a-1 + +`apidom-ns-a2a-1` contains the ApiDOM namespace for the [A2A (Agent-to-Agent) Protocol v1.0](https://a2a-protocol.org/latest/definitions/). It models the **Agent Card** document type — the `/.well-known/agent.json` manifest that describes an agent's identity, capabilities, skills, supported interfaces, and security requirements. + +## Installation + +```sh +npm install --save @swagger-api/apidom-ns-a2a-1 +``` + +## Usage + +```ts +import { AgentCardElement, isAgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; + +const card = AgentCardElement.refract({ + name: 'Recipe Agent', + description: 'Helps users find and follow recipes', + url: 'https://recipes.example.com/a2a', + version: '1.0.0', + capabilities: { + streaming: true, + pushNotifications: false, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + skills: [ + { + id: 'find-recipe', + name: 'Find Recipe', + description: 'Locate recipes matching ingredients or cuisine.', + tags: ['recipes', 'cooking'], + }, + ], +}); + +isAgentCardElement(card); // true +card.name?.toValue(); // "Recipe Agent" +card.capabilities?.streaming?.toValue(); // true +``` + +## Supported elements + +`AgentCard`, `AgentCapabilities`, `AgentExtension`, `AgentProvider`, `AgentInterface`, `AgentSkill`, `AgentCardSignature`, `SecurityRequirement`, `SecurityScheme` (wrapper) and its five concrete subtypes (`APIKey`, `HTTPAuth`, `MutualTls`, `OAuth2`, `OpenIdConnect`), `OAuthFlows` and its five flow subtypes (`AuthorizationCode`, `ClientCredentials`, `DeviceCode`, `Implicit`, `Password`), and `StringList`. + +## Implementation notes + +- **Source of truth.** A2A's normative spec is the [Protocol Buffers definition](https://github.com/a2aproject/A2A). The [JSON Schema bundle](https://a2a-protocol.org/latest/spec/a2a.json) used here is non-normative and machine-generated from the `.proto` files. Use the `.proto` to resolve ambiguities. + +- **camelCase canonicalisation.** A2A's JSON encoding allows both camelCase and snake_case property names (a protobuf JSON convention). Element classes expose camelCase getters/setters. Snake_case keys for the 28 dual-named fields in the A2A schema are canonicalised to camelCase by `refractor/canonicalize.ts` before refraction, so both spellings refract to the same tree. + +- **SecurityScheme is a wrapper.** The A2A schema models `SecurityScheme` as a protobuf `oneof` — a wrapper object with five named optional subfields (`apiKeySecurityScheme`, `httpAuthSecurityScheme`, `mtlsSecurityScheme`, `oauth2SecurityScheme`, `openIdConnectSecurityScheme`). It is not `type`-discriminated like OpenAPI's SecurityScheme. + +- **Scope.** This namespace models the AgentCard *document*. Wire-protocol messages (JSON-RPC requests, responses, errors; Task, Message, Artifact types) live in the same A2A schema but are not modelled here. + +- **Media types.** A2A has no IANA-registered media type. This namespace uses a `application/vnd.a2a;version=1.0.0` convention; revisit when/if A2A registers an official one. + +## License + +Apache-2.0 diff --git a/packages/apidom-ns-a2a-1/config/api-extractor/api-extractor.json b/packages/apidom-ns-a2a-1/config/api-extractor/api-extractor.json new file mode 100644 index 0000000000..7de0d99447 --- /dev/null +++ b/packages/apidom-ns-a2a-1/config/api-extractor/api-extractor.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../../../api-extractor.json" +} diff --git a/packages/apidom-ns-a2a-1/package.json b/packages/apidom-ns-a2a-1/package.json new file mode 100644 index 0000000000..699e3c0de3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/package.json @@ -0,0 +1,61 @@ +{ + "name": "@swagger-api/apidom-ns-a2a-1", + "version": "1.11.1", + "description": "A2A (Agent-to-Agent) Protocol v1 namespace for ApiDOM.", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "type": "module", + "sideEffects": [ + "./src/refractor/registration.mjs", + "./src/refractor/registration.cjs" + ], + "unpkg": "./dist/apidom-ns-a2a-1.browser.min.js", + "main": "./src/index.cjs", + "exports": { + "types": "./types/apidom-ns-a2a-1.d.ts", + "import": "./src/index.mjs", + "require": "./src/index.cjs" + }, + "types": "./types/apidom-ns-a2a-1.d.ts", + "scripts": { + "build": "npm run clean && run-p --max-parallel ${CPU_CORES:-6} typescript:declaration build:es build:cjs build:umd:browser", + "build:es": "cross-env BABEL_ENV=es babel src --out-dir src --extensions '.ts' --out-file-extension '.mjs' --root-mode 'upward'", + "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir src --extensions '.ts' --out-file-extension '.cjs' --root-mode 'upward'", + "build:umd:browser": "vite build", + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix", + "clean": "rimraf --glob 'src/**/*.mjs' 'test/**/*.mjs' ./dist ./types", + "test": "NODE_ENV=test ts-mocha --exit", + "test:update-snapshots": "cross-env UPDATE_SNAPSHOT=1 BABEL_ENV=cjs mocha", + "typescript:check-types": "tsc --noEmit && tsc -p ./test/tsconfig.json --noEmit", + "typescript:declaration": "tsc -p tsconfig.declaration.json && api-extractor run -l -c ./config/api-extractor/api-extractor.json 2>&1 | shx grep -v 'Visitor_base'", + "prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .", + "postpack": "rimraf NOTICE LICENSES" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/swagger-api/apidom.git" + }, + "author": "SmartBear", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + }, + "files": [ + "src/**/*.mjs", + "src/**/*.cjs", + "dist/", + "types/apidom-ns-a2a-1.d.ts", + "LICENSES", + "NOTICE", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/apidom-ns-a2a-1/src/elements/APIKeySecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/APIKeySecurityScheme.ts new file mode 100644 index 0000000000..65248b4b1e --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/APIKeySecurityScheme.ts @@ -0,0 +1,38 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class APIKeySecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'apiKeySecurityScheme'; + this.classes.push('api-key-security-scheme'); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get name(): StringElement | undefined { + return this.get('name'); + } + + set name(name: StringElement | undefined) { + this.set('name', name); + } + + get location(): StringElement | undefined { + return this.get('location'); + } + + set location(location: StringElement | undefined) { + this.set('location', location); + } +} + +export default APIKeySecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentCapabilities.ts b/packages/apidom-ns-a2a-1/src/elements/AgentCapabilities.ts new file mode 100644 index 0000000000..3eee845068 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentCapabilities.ts @@ -0,0 +1,52 @@ +import { + ArrayElement, + BooleanElement, + ObjectElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentCapabilities extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentCapabilities'; + this.classes.push('agent-capabilities'); + } + + get streaming(): BooleanElement | undefined { + return this.get('streaming'); + } + + set streaming(streaming: BooleanElement | undefined) { + this.set('streaming', streaming); + } + + get pushNotifications(): BooleanElement | undefined { + return this.get('pushNotifications'); + } + + set pushNotifications(pushNotifications: BooleanElement | undefined) { + this.set('pushNotifications', pushNotifications); + } + + get extendedAgentCard(): BooleanElement | undefined { + return this.get('extendedAgentCard'); + } + + set extendedAgentCard(extendedAgentCard: BooleanElement | undefined) { + this.set('extendedAgentCard', extendedAgentCard); + } + + get extensions(): ArrayElement | undefined { + return this.get('extensions'); + } + + set extensions(extensions: ArrayElement | undefined) { + this.set('extensions', extensions); + } +} + +export default AgentCapabilities; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentCard.ts b/packages/apidom-ns-a2a-1/src/elements/AgentCard.ts new file mode 100644 index 0000000000..2057f74f70 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentCard.ts @@ -0,0 +1,138 @@ +import { + ArrayElement, + ObjectElement, + StringElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +import AgentCapabilitiesElement from './AgentCapabilities.ts'; +import AgentProviderElement from './AgentProvider.ts'; + +/** + * @public + * + * Root element for an A2A Agent Card document. + */ +class AgentCard extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'a2aAgentCard1'; + this.classes.push('api'); + this.classes.push('agent-card'); + } + + get name(): StringElement | undefined { + return this.get('name'); + } + + set name(name: StringElement | undefined) { + this.set('name', name); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get version(): StringElement | undefined { + return this.get('version'); + } + + set version(version: StringElement | undefined) { + this.set('version', version); + } + + get iconUrl(): StringElement | undefined { + return this.get('iconUrl'); + } + + set iconUrl(iconUrl: StringElement | undefined) { + this.set('iconUrl', iconUrl); + } + + get documentationUrl(): StringElement | undefined { + return this.get('documentationUrl'); + } + + set documentationUrl(documentationUrl: StringElement | undefined) { + this.set('documentationUrl', documentationUrl); + } + + get provider(): AgentProviderElement | undefined { + return this.get('provider'); + } + + set provider(provider: AgentProviderElement | undefined) { + this.set('provider', provider); + } + + get capabilities(): AgentCapabilitiesElement | undefined { + return this.get('capabilities'); + } + + set capabilities(capabilities: AgentCapabilitiesElement | undefined) { + this.set('capabilities', capabilities); + } + + get defaultInputModes(): ArrayElement | undefined { + return this.get('defaultInputModes'); + } + + set defaultInputModes(defaultInputModes: ArrayElement | undefined) { + this.set('defaultInputModes', defaultInputModes); + } + + get defaultOutputModes(): ArrayElement | undefined { + return this.get('defaultOutputModes'); + } + + set defaultOutputModes(defaultOutputModes: ArrayElement | undefined) { + this.set('defaultOutputModes', defaultOutputModes); + } + + get supportedInterfaces(): ArrayElement | undefined { + return this.get('supportedInterfaces'); + } + + set supportedInterfaces(supportedInterfaces: ArrayElement | undefined) { + this.set('supportedInterfaces', supportedInterfaces); + } + + get skills(): ArrayElement | undefined { + return this.get('skills'); + } + + set skills(skills: ArrayElement | undefined) { + this.set('skills', skills); + } + + get securitySchemes(): ObjectElement | undefined { + return this.get('securitySchemes'); + } + + set securitySchemes(securitySchemes: ObjectElement | undefined) { + this.set('securitySchemes', securitySchemes); + } + + get securityRequirements(): ArrayElement | undefined { + return this.get('securityRequirements'); + } + + set securityRequirements(securityRequirements: ArrayElement | undefined) { + this.set('securityRequirements', securityRequirements); + } + + get signatures(): ArrayElement | undefined { + return this.get('signatures'); + } + + set signatures(signatures: ArrayElement | undefined) { + this.set('signatures', signatures); + } +} + +export default AgentCard; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentCardSignature.ts b/packages/apidom-ns-a2a-1/src/elements/AgentCardSignature.ts new file mode 100644 index 0000000000..bfe62571f0 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentCardSignature.ts @@ -0,0 +1,38 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentCardSignature extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentCardSignature'; + this.classes.push('agent-card-signature'); + } + + get protected(): StringElement | undefined { + return this.get('protected'); + } + + set protected(protectedValue: StringElement | undefined) { + this.set('protected', protectedValue); + } + + get signature(): StringElement | undefined { + return this.get('signature'); + } + + set signature(signature: StringElement | undefined) { + this.set('signature', signature); + } + + get header(): ObjectElement | undefined { + return this.get('header'); + } + + set header(header: ObjectElement | undefined) { + this.set('header', header); + } +} + +export default AgentCardSignature; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentExtension.ts b/packages/apidom-ns-a2a-1/src/elements/AgentExtension.ts new file mode 100644 index 0000000000..314affaf66 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentExtension.ts @@ -0,0 +1,52 @@ +import { + StringElement, + BooleanElement, + ObjectElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentExtension extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentExtension'; + this.classes.push('agent-extension'); + } + + get uri(): StringElement | undefined { + return this.get('uri'); + } + + set uri(uri: StringElement | undefined) { + this.set('uri', uri); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get required(): BooleanElement | undefined { + return this.get('required'); + } + + set required(required: BooleanElement | undefined) { + this.set('required', required); + } + + get params(): ObjectElement | undefined { + return this.get('params'); + } + + set params(params: ObjectElement | undefined) { + this.set('params', params); + } +} + +export default AgentExtension; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentInterface.ts b/packages/apidom-ns-a2a-1/src/elements/AgentInterface.ts new file mode 100644 index 0000000000..1ec7f95880 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentInterface.ts @@ -0,0 +1,46 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentInterface extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentInterface'; + this.classes.push('agent-interface'); + } + + get url(): StringElement | undefined { + return this.get('url'); + } + + set url(url: StringElement | undefined) { + this.set('url', url); + } + + get protocolBinding(): StringElement | undefined { + return this.get('protocolBinding'); + } + + set protocolBinding(protocolBinding: StringElement | undefined) { + this.set('protocolBinding', protocolBinding); + } + + get protocolVersion(): StringElement | undefined { + return this.get('protocolVersion'); + } + + set protocolVersion(protocolVersion: StringElement | undefined) { + this.set('protocolVersion', protocolVersion); + } + + get tenant(): StringElement | undefined { + return this.get('tenant'); + } + + set tenant(tenant: StringElement | undefined) { + this.set('tenant', tenant); + } +} + +export default AgentInterface; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentProvider.ts b/packages/apidom-ns-a2a-1/src/elements/AgentProvider.ts new file mode 100644 index 0000000000..5289809f7e --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentProvider.ts @@ -0,0 +1,30 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentProvider extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentProvider'; + this.classes.push('agent-provider'); + } + + get organization(): StringElement | undefined { + return this.get('organization'); + } + + set organization(organization: StringElement | undefined) { + this.set('organization', organization); + } + + get url(): StringElement | undefined { + return this.get('url'); + } + + set url(url: StringElement | undefined) { + this.set('url', url); + } +} + +export default AgentProvider; diff --git a/packages/apidom-ns-a2a-1/src/elements/AgentSkill.ts b/packages/apidom-ns-a2a-1/src/elements/AgentSkill.ts new file mode 100644 index 0000000000..745b55156c --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AgentSkill.ts @@ -0,0 +1,76 @@ +import { + ArrayElement, + ObjectElement, + StringElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AgentSkill extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'agentSkill'; + this.classes.push('agent-skill'); + } + + get name(): StringElement | undefined { + return this.get('name'); + } + + set name(name: StringElement | undefined) { + this.set('name', name); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get tags(): ArrayElement | undefined { + return this.get('tags'); + } + + set tags(tags: ArrayElement | undefined) { + this.set('tags', tags); + } + + get examples(): ArrayElement | undefined { + return this.get('examples'); + } + + set examples(examples: ArrayElement | undefined) { + this.set('examples', examples); + } + + get inputModes(): ArrayElement | undefined { + return this.get('inputModes'); + } + + set inputModes(inputModes: ArrayElement | undefined) { + this.set('inputModes', inputModes); + } + + get outputModes(): ArrayElement | undefined { + return this.get('outputModes'); + } + + set outputModes(outputModes: ArrayElement | undefined) { + this.set('outputModes', outputModes); + } + + get securityRequirements(): ArrayElement | undefined { + return this.get('securityRequirements'); + } + + set securityRequirements(securityRequirements: ArrayElement | undefined) { + this.set('securityRequirements', securityRequirements); + } +} + +export default AgentSkill; diff --git a/packages/apidom-ns-a2a-1/src/elements/AuthorizationCodeOAuthFlow.ts b/packages/apidom-ns-a2a-1/src/elements/AuthorizationCodeOAuthFlow.ts new file mode 100644 index 0000000000..d95463b19c --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/AuthorizationCodeOAuthFlow.ts @@ -0,0 +1,61 @@ +import { + BooleanElement, + ObjectElement, + StringElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +/** + * @public + */ +class AuthorizationCodeOAuthFlow extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'authorizationCodeOAuthFlow'; + this.classes.push('authorization-code-oauth-flow'); + this.classes.push('oauth-flow'); + } + + get authorizationUrl(): StringElement | undefined { + return this.get('authorizationUrl'); + } + + set authorizationUrl(authorizationUrl: StringElement | undefined) { + this.set('authorizationUrl', authorizationUrl); + } + + get tokenUrl(): StringElement | undefined { + return this.get('tokenUrl'); + } + + set tokenUrl(tokenUrl: StringElement | undefined) { + this.set('tokenUrl', tokenUrl); + } + + get refreshUrl(): StringElement | undefined { + return this.get('refreshUrl'); + } + + set refreshUrl(refreshUrl: StringElement | undefined) { + this.set('refreshUrl', refreshUrl); + } + + get pkceRequired(): BooleanElement | undefined { + return this.get('pkceRequired'); + } + + set pkceRequired(pkceRequired: BooleanElement | undefined) { + this.set('pkceRequired', pkceRequired); + } + + get scopes(): ObjectElement | undefined { + return this.get('scopes'); + } + + set scopes(scopes: ObjectElement | undefined) { + this.set('scopes', scopes); + } +} + +export default AuthorizationCodeOAuthFlow; diff --git a/packages/apidom-ns-a2a-1/src/elements/ClientCredentialsOAuthFlow.ts b/packages/apidom-ns-a2a-1/src/elements/ClientCredentialsOAuthFlow.ts new file mode 100644 index 0000000000..d120168020 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/ClientCredentialsOAuthFlow.ts @@ -0,0 +1,39 @@ +import { ObjectElement, StringElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class ClientCredentialsOAuthFlow extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'clientCredentialsOAuthFlow'; + this.classes.push('client-credentials-oauth-flow'); + this.classes.push('oauth-flow'); + } + + get tokenUrl(): StringElement | undefined { + return this.get('tokenUrl'); + } + + set tokenUrl(tokenUrl: StringElement | undefined) { + this.set('tokenUrl', tokenUrl); + } + + get refreshUrl(): StringElement | undefined { + return this.get('refreshUrl'); + } + + set refreshUrl(refreshUrl: StringElement | undefined) { + this.set('refreshUrl', refreshUrl); + } + + get scopes(): ObjectElement | undefined { + return this.get('scopes'); + } + + set scopes(scopes: ObjectElement | undefined) { + this.set('scopes', scopes); + } +} + +export default ClientCredentialsOAuthFlow; diff --git a/packages/apidom-ns-a2a-1/src/elements/DeviceCodeOAuthFlow.ts b/packages/apidom-ns-a2a-1/src/elements/DeviceCodeOAuthFlow.ts new file mode 100644 index 0000000000..12b2e99675 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/DeviceCodeOAuthFlow.ts @@ -0,0 +1,47 @@ +import { ObjectElement, StringElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class DeviceCodeOAuthFlow extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'deviceCodeOAuthFlow'; + this.classes.push('device-code-oauth-flow'); + this.classes.push('oauth-flow'); + } + + get deviceAuthorizationUrl(): StringElement | undefined { + return this.get('deviceAuthorizationUrl'); + } + + set deviceAuthorizationUrl(deviceAuthorizationUrl: StringElement | undefined) { + this.set('deviceAuthorizationUrl', deviceAuthorizationUrl); + } + + get tokenUrl(): StringElement | undefined { + return this.get('tokenUrl'); + } + + set tokenUrl(tokenUrl: StringElement | undefined) { + this.set('tokenUrl', tokenUrl); + } + + get refreshUrl(): StringElement | undefined { + return this.get('refreshUrl'); + } + + set refreshUrl(refreshUrl: StringElement | undefined) { + this.set('refreshUrl', refreshUrl); + } + + get scopes(): ObjectElement | undefined { + return this.get('scopes'); + } + + set scopes(scopes: ObjectElement | undefined) { + this.set('scopes', scopes); + } +} + +export default DeviceCodeOAuthFlow; diff --git a/packages/apidom-ns-a2a-1/src/elements/HTTPAuthSecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/HTTPAuthSecurityScheme.ts new file mode 100644 index 0000000000..5f6ea3a05b --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/HTTPAuthSecurityScheme.ts @@ -0,0 +1,38 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class HTTPAuthSecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'httpAuthSecurityScheme'; + this.classes.push('http-auth-security-scheme'); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get scheme(): StringElement | undefined { + return this.get('scheme'); + } + + set scheme(scheme: StringElement | undefined) { + this.set('scheme', scheme); + } + + get bearerFormat(): StringElement | undefined { + return this.get('bearerFormat'); + } + + set bearerFormat(bearerFormat: StringElement | undefined) { + this.set('bearerFormat', bearerFormat); + } +} + +export default HTTPAuthSecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/MutualTlsSecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/MutualTlsSecurityScheme.ts new file mode 100644 index 0000000000..e94e437d69 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/MutualTlsSecurityScheme.ts @@ -0,0 +1,22 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class MutualTlsSecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'mutualTlsSecurityScheme'; + this.classes.push('mutual-tls-security-scheme'); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } +} + +export default MutualTlsSecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/OAuth2SecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/OAuth2SecurityScheme.ts new file mode 100644 index 0000000000..c39e4b1dd7 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/OAuth2SecurityScheme.ts @@ -0,0 +1,40 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +import OAuthFlowsElement from './OAuthFlows.ts'; + +/** + * @public + */ +class OAuth2SecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'oauth2SecurityScheme'; + this.classes.push('oauth2-security-scheme'); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get flows(): OAuthFlowsElement | undefined { + return this.get('flows'); + } + + set flows(flows: OAuthFlowsElement | undefined) { + this.set('flows', flows); + } + + get oauth2MetadataUrl(): StringElement | undefined { + return this.get('oauth2MetadataUrl'); + } + + set oauth2MetadataUrl(oauth2MetadataUrl: StringElement | undefined) { + this.set('oauth2MetadataUrl', oauth2MetadataUrl); + } +} + +export default OAuth2SecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/OAuthFlows.ts b/packages/apidom-ns-a2a-1/src/elements/OAuthFlows.ts new file mode 100644 index 0000000000..c48e623d06 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/OAuthFlows.ts @@ -0,0 +1,45 @@ +import { ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +import AuthorizationCodeOAuthFlowElement from './AuthorizationCodeOAuthFlow.ts'; +import ClientCredentialsOAuthFlowElement from './ClientCredentialsOAuthFlow.ts'; +import DeviceCodeOAuthFlowElement from './DeviceCodeOAuthFlow.ts'; + +/** + * @public + * + * Container for the three OAuth 2.0 flow configurations. Any subset may be + * populated at once (this is not a discriminated union). + */ +class OAuthFlows extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'oauthFlows'; + this.classes.push('oauth-flows'); + } + + get authorizationCode(): AuthorizationCodeOAuthFlowElement | undefined { + return this.get('authorizationCode'); + } + + set authorizationCode(authorizationCode: AuthorizationCodeOAuthFlowElement | undefined) { + this.set('authorizationCode', authorizationCode); + } + + get clientCredentials(): ClientCredentialsOAuthFlowElement | undefined { + return this.get('clientCredentials'); + } + + set clientCredentials(clientCredentials: ClientCredentialsOAuthFlowElement | undefined) { + this.set('clientCredentials', clientCredentials); + } + + get deviceCode(): DeviceCodeOAuthFlowElement | undefined { + return this.get('deviceCode'); + } + + set deviceCode(deviceCode: DeviceCodeOAuthFlowElement | undefined) { + this.set('deviceCode', deviceCode); + } +} + +export default OAuthFlows; diff --git a/packages/apidom-ns-a2a-1/src/elements/OpenIdConnectSecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/OpenIdConnectSecurityScheme.ts new file mode 100644 index 0000000000..437fbf1181 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/OpenIdConnectSecurityScheme.ts @@ -0,0 +1,30 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class OpenIdConnectSecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'openIdConnectSecurityScheme'; + this.classes.push('open-id-connect-security-scheme'); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get openIdConnectUrl(): StringElement | undefined { + return this.get('openIdConnectUrl'); + } + + set openIdConnectUrl(openIdConnectUrl: StringElement | undefined) { + this.set('openIdConnectUrl', openIdConnectUrl); + } +} + +export default OpenIdConnectSecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/SecurityRequirement.ts b/packages/apidom-ns-a2a-1/src/elements/SecurityRequirement.ts new file mode 100644 index 0000000000..c9b94fb4d4 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/SecurityRequirement.ts @@ -0,0 +1,25 @@ +import { ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + * + * The `schemes` map's keys are security scheme names; its values are + * StringList elements containing required scopes. + */ +class SecurityRequirement extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'securityRequirement'; + this.classes.push('security-requirement'); + } + + get schemes(): ObjectElement | undefined { + return this.get('schemes'); + } + + set schemes(schemes: ObjectElement | undefined) { + this.set('schemes', schemes); + } +} + +export default SecurityRequirement; diff --git a/packages/apidom-ns-a2a-1/src/elements/SecurityScheme.ts b/packages/apidom-ns-a2a-1/src/elements/SecurityScheme.ts new file mode 100644 index 0000000000..6307068b4d --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/SecurityScheme.ts @@ -0,0 +1,68 @@ +import { ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +import APIKeySecuritySchemeElement from './APIKeySecurityScheme.ts'; +import HTTPAuthSecuritySchemeElement from './HTTPAuthSecurityScheme.ts'; +import MutualTlsSecuritySchemeElement from './MutualTlsSecurityScheme.ts'; +import OAuth2SecuritySchemeElement from './OAuth2SecurityScheme.ts'; +import OpenIdConnectSecuritySchemeElement from './OpenIdConnectSecurityScheme.ts'; + +/** + * @public + * + * Wrapper object for one of five concrete security scheme types. The A2A + * schema models this as a protobuf `oneof` rather than a `type`-discriminated + * union: exactly one of the five subobject fields is expected to be present. + * No conditional visitor is needed — each field refracts to its specific + * concrete element type. + */ +class SecurityScheme extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'securityScheme'; + this.classes.push('security-scheme'); + } + + get apiKeySecurityScheme(): APIKeySecuritySchemeElement | undefined { + return this.get('apiKeySecurityScheme'); + } + + set apiKeySecurityScheme(apiKeySecurityScheme: APIKeySecuritySchemeElement | undefined) { + this.set('apiKeySecurityScheme', apiKeySecurityScheme); + } + + get httpAuthSecurityScheme(): HTTPAuthSecuritySchemeElement | undefined { + return this.get('httpAuthSecurityScheme'); + } + + set httpAuthSecurityScheme(httpAuthSecurityScheme: HTTPAuthSecuritySchemeElement | undefined) { + this.set('httpAuthSecurityScheme', httpAuthSecurityScheme); + } + + get mtlsSecurityScheme(): MutualTlsSecuritySchemeElement | undefined { + return this.get('mtlsSecurityScheme'); + } + + set mtlsSecurityScheme(mtlsSecurityScheme: MutualTlsSecuritySchemeElement | undefined) { + this.set('mtlsSecurityScheme', mtlsSecurityScheme); + } + + get oauth2SecurityScheme(): OAuth2SecuritySchemeElement | undefined { + return this.get('oauth2SecurityScheme'); + } + + set oauth2SecurityScheme(oauth2SecurityScheme: OAuth2SecuritySchemeElement | undefined) { + this.set('oauth2SecurityScheme', oauth2SecurityScheme); + } + + get openIdConnectSecurityScheme(): OpenIdConnectSecuritySchemeElement | undefined { + return this.get('openIdConnectSecurityScheme'); + } + + set openIdConnectSecurityScheme( + openIdConnectSecurityScheme: OpenIdConnectSecuritySchemeElement | undefined, + ) { + this.set('openIdConnectSecurityScheme', openIdConnectSecurityScheme); + } +} + +export default SecurityScheme; diff --git a/packages/apidom-ns-a2a-1/src/elements/StringList.ts b/packages/apidom-ns-a2a-1/src/elements/StringList.ts new file mode 100644 index 0000000000..6bf02f9a0b --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/StringList.ts @@ -0,0 +1,26 @@ +import { ArrayElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + * + * A2A's StringList is a wrapper object with a single `list` array property + * (modelled this way in the schema to satisfy protobuf's repeated-string + * encoding inside a map value). + */ +class StringList extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'stringList'; + this.classes.push('string-list'); + } + + get list(): ArrayElement | undefined { + return this.get('list'); + } + + set list(list: ArrayElement | undefined) { + this.set('list', list); + } +} + +export default StringList; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/Extensions.ts b/packages/apidom-ns-a2a-1/src/elements/nces/Extensions.ts new file mode 100644 index 0000000000..9bab3bc33b --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/Extensions.ts @@ -0,0 +1,15 @@ +import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class Extensions extends ArrayElement { + static primaryClass = 'extensions'; + + constructor(content?: Array, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(Extensions.primaryClass); + } +} + +export default Extensions; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/SecurityRequirements.ts b/packages/apidom-ns-a2a-1/src/elements/nces/SecurityRequirements.ts new file mode 100644 index 0000000000..284d82104c --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/SecurityRequirements.ts @@ -0,0 +1,15 @@ +import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class SecurityRequirements extends ArrayElement { + static primaryClass = 'security-requirements'; + + constructor(content?: Array, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(SecurityRequirements.primaryClass); + } +} + +export default SecurityRequirements; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/SecuritySchemes.ts b/packages/apidom-ns-a2a-1/src/elements/nces/SecuritySchemes.ts new file mode 100644 index 0000000000..4b28cf0028 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/SecuritySchemes.ts @@ -0,0 +1,15 @@ +import { ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class SecuritySchemes extends ObjectElement { + static primaryClass = 'security-schemes'; + + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(SecuritySchemes.primaryClass); + } +} + +export default SecuritySchemes; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/Signatures.ts b/packages/apidom-ns-a2a-1/src/elements/nces/Signatures.ts new file mode 100644 index 0000000000..1814f76112 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/Signatures.ts @@ -0,0 +1,15 @@ +import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class Signatures extends ArrayElement { + static primaryClass = 'signatures'; + + constructor(content?: Array, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(Signatures.primaryClass); + } +} + +export default Signatures; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/Skills.ts b/packages/apidom-ns-a2a-1/src/elements/nces/Skills.ts new file mode 100644 index 0000000000..df22759736 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/Skills.ts @@ -0,0 +1,15 @@ +import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class Skills extends ArrayElement { + static primaryClass = 'skills'; + + constructor(content?: Array, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(Skills.primaryClass); + } +} + +export default Skills; diff --git a/packages/apidom-ns-a2a-1/src/elements/nces/SupportedInterfaces.ts b/packages/apidom-ns-a2a-1/src/elements/nces/SupportedInterfaces.ts new file mode 100644 index 0000000000..eb56f6afd4 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/elements/nces/SupportedInterfaces.ts @@ -0,0 +1,15 @@ +import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +/** + * @public + */ +class SupportedInterfaces extends ArrayElement { + static primaryClass = 'supported-interfaces'; + + constructor(content?: Array, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.classes.push(SupportedInterfaces.primaryClass); + } +} + +export default SupportedInterfaces; diff --git a/packages/apidom-ns-a2a-1/src/index.ts b/packages/apidom-ns-a2a-1/src/index.ts new file mode 100644 index 0000000000..3092c4ac00 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/index.ts @@ -0,0 +1,214 @@ +export { + isRefElement, + isLinkElement as isLinkPrimitiveElement, + isMemberElement, + isObjectElement, + isArrayElement, + isBooleanElement, + isNullElement, + isElement, + isNumberElement, + isStringElement, +} from '@swagger-api/apidom-core'; + +export { default as mediaTypes, A2AMediaTypes } from './media-types.ts'; +export type { Format } from './media-types.ts'; + +// eslint-disable-next-line no-restricted-exports +export { default } from './namespace.ts'; + +export { default as refract, createRefractor } from './refractor/index.ts'; +export { default as specificationObj } from './refractor/specification.ts'; +export { default as refractorPluginReplaceEmptyElement } from './refractor/plugins/replace-empty-element.ts'; + +// Generic visitor base classes +export { default as FixedFieldsVisitor } from './refractor/visitors/generics/FixedFieldsVisitor.ts'; +export type { + FixedFieldsVisitorOptions, + SpecPath, +} from './refractor/visitors/generics/FixedFieldsVisitor.ts'; +export { default as MapVisitor } from './refractor/visitors/generics/MapVisitor.ts'; +export type { MapVisitorOptions } from './refractor/visitors/generics/MapVisitor.ts'; +export { default as PatternedFieldsVisitor } from './refractor/visitors/generics/PatternedFieldsVisitor.ts'; +export type { PatternedFieldsVisitorOptions } from './refractor/visitors/generics/PatternedFieldsVisitor.ts'; +export { default as FallbackVisitor } from './refractor/visitors/FallbackVisitor.ts'; +export type { FallbackVisitorOptions } from './refractor/visitors/FallbackVisitor.ts'; +export { default as SpecificationExtensionVisitor } from './refractor/visitors/SpecificationExtensionVisitor.ts'; +export type { SpecificationExtensionVisitorOptions } from './refractor/visitors/SpecificationExtensionVisitor.ts'; +export { default as SpecificationVisitor } from './refractor/visitors/SpecificationVisitor.ts'; +export type { SpecificationVisitorOptions } from './refractor/visitors/SpecificationVisitor.ts'; +export { default as Visitor } from './refractor/visitors/Visitor.ts'; +export type { VisitorOptions } from './refractor/visitors/Visitor.ts'; + +// A2A v1 element visitors (per-element FixedFields visitors) +export type { + default as AgentCardVisitor, + AgentCardVisitorOptions, +} from './refractor/visitors/a2a-1/agent-card/index.ts'; +export type { + default as AgentCapabilitiesVisitor, + AgentCapabilitiesVisitorOptions, +} from './refractor/visitors/a2a-1/agent-capabilities/index.ts'; +export type { + default as AgentExtensionVisitor, + AgentExtensionVisitorOptions, +} from './refractor/visitors/a2a-1/agent-extension/index.ts'; +export type { + default as AgentProviderVisitor, + AgentProviderVisitorOptions, +} from './refractor/visitors/a2a-1/agent-provider/index.ts'; +export type { + default as AgentInterfaceVisitor, + AgentInterfaceVisitorOptions, +} from './refractor/visitors/a2a-1/agent-interface/index.ts'; +export type { + default as AgentSkillVisitor, + AgentSkillVisitorOptions, +} from './refractor/visitors/a2a-1/agent-skill/index.ts'; +export type { + default as AgentCardSignatureVisitor, + AgentCardSignatureVisitorOptions, +} from './refractor/visitors/a2a-1/agent-card-signature/index.ts'; +export type { + default as SecurityRequirementVisitor, + SecurityRequirementVisitorOptions, +} from './refractor/visitors/a2a-1/security-requirement/index.ts'; +export type { + default as SecuritySchemeVisitor, + SecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/security-scheme/index.ts'; +export type { + default as APIKeySecuritySchemeVisitor, + APIKeySecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/api-key-security-scheme/index.ts'; +export type { + default as HTTPAuthSecuritySchemeVisitor, + HTTPAuthSecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/http-auth-security-scheme/index.ts'; +export type { + default as MutualTlsSecuritySchemeVisitor, + MutualTlsSecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/mutual-tls-security-scheme/index.ts'; +export type { + default as OAuth2SecuritySchemeVisitor, + OAuth2SecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/oauth2-security-scheme/index.ts'; +export type { + default as OpenIdConnectSecuritySchemeVisitor, + OpenIdConnectSecuritySchemeVisitorOptions, +} from './refractor/visitors/a2a-1/open-id-connect-security-scheme/index.ts'; +export type { + default as OAuthFlowsVisitor, + OAuthFlowsVisitorOptions, +} from './refractor/visitors/a2a-1/oauth-flows/index.ts'; +export type { + default as AuthorizationCodeOAuthFlowVisitor, + AuthorizationCodeOAuthFlowVisitorOptions, +} from './refractor/visitors/a2a-1/authorization-code-oauth-flow/index.ts'; +export type { + default as ClientCredentialsOAuthFlowVisitor, + ClientCredentialsOAuthFlowVisitorOptions, +} from './refractor/visitors/a2a-1/client-credentials-oauth-flow/index.ts'; +export type { + default as DeviceCodeOAuthFlowVisitor, + DeviceCodeOAuthFlowVisitorOptions, +} from './refractor/visitors/a2a-1/device-code-oauth-flow/index.ts'; +export type { + default as StringListVisitor, + StringListVisitorOptions, +} from './refractor/visitors/a2a-1/string-list/index.ts'; + +// Array/Map visitors +export type { + default as SkillsVisitor, + SkillsVisitorOptions, +} from './refractor/visitors/a2a-1/SkillsVisitor.ts'; +export type { + default as SignaturesVisitor, + SignaturesVisitorOptions, +} from './refractor/visitors/a2a-1/SignaturesVisitor.ts'; +export type { + default as SupportedInterfacesVisitor, + SupportedInterfacesVisitorOptions, +} from './refractor/visitors/a2a-1/SupportedInterfacesVisitor.ts'; +export type { + default as SecurityRequirementsVisitor, + SecurityRequirementsVisitorOptions, +} from './refractor/visitors/a2a-1/SecurityRequirementsVisitor.ts'; +export type { + default as ExtensionsVisitor, + ExtensionsVisitorOptions, +} from './refractor/visitors/a2a-1/ExtensionsVisitor.ts'; +export type { + default as SecuritySchemesVisitor, + SecuritySchemesVisitorOptions, +} from './refractor/visitors/a2a-1/SecuritySchemesVisitor.ts'; +export type { + default as SecurityRequirementSchemesVisitor, + SecurityRequirementSchemesVisitorOptions, +} from './refractor/visitors/a2a-1/security-requirement/SchemesVisitor.ts'; + +// Predicates +export { + isAgentCardElement, + isAgentCapabilitiesElement, + isAgentExtensionElement, + isAgentProviderElement, + isAgentInterfaceElement, + isAgentSkillElement, + isAgentCardSignatureElement, + isSecurityRequirementElement, + isSecuritySchemeElement, + isAPIKeySecuritySchemeElement, + isHTTPAuthSecuritySchemeElement, + isMutualTlsSecuritySchemeElement, + isOAuth2SecuritySchemeElement, + isOpenIdConnectSecuritySchemeElement, + isOAuthFlowsElement, + isAuthorizationCodeOAuthFlowElement, + isClientCredentialsOAuthFlowElement, + isDeviceCodeOAuthFlowElement, + isStringListElement, + isSkillsElement, + isSignaturesElement, + isSupportedInterfacesElement, + isSecurityRequirementsElement, + isExtensionsElement, + isSecuritySchemesElement, +} from './predicates.ts'; + +export { isA2ASpecificationExtension } from './refractor/predicates.ts'; + +export { keyMap, getNodeType } from './traversal/visitor.ts'; + +// NCE element classes (Named Collection Elements: ArrayElement / ObjectElement +// subclasses with distinct CSS classes for typed predicates) +export { default as SkillsElement } from './elements/nces/Skills.ts'; +export { default as SignaturesElement } from './elements/nces/Signatures.ts'; +export { default as SupportedInterfacesElement } from './elements/nces/SupportedInterfaces.ts'; +export { default as SecurityRequirementsElement } from './elements/nces/SecurityRequirements.ts'; +export { default as ExtensionsElement } from './elements/nces/Extensions.ts'; +export { default as SecuritySchemesElement } from './elements/nces/SecuritySchemes.ts'; + +// A2A v1 elements (with .refract attached via registration) +export { + AgentCardElement, + AgentCapabilitiesElement, + AgentExtensionElement, + AgentProviderElement, + AgentInterfaceElement, + AgentSkillElement, + AgentCardSignatureElement, + SecurityRequirementElement, + SecuritySchemeElement, + APIKeySecuritySchemeElement, + HTTPAuthSecuritySchemeElement, + MutualTlsSecuritySchemeElement, + OAuth2SecuritySchemeElement, + OpenIdConnectSecuritySchemeElement, + OAuthFlowsElement, + AuthorizationCodeOAuthFlowElement, + ClientCredentialsOAuthFlowElement, + DeviceCodeOAuthFlowElement, + StringListElement, +} from './refractor/registration.ts'; diff --git a/packages/apidom-ns-a2a-1/src/media-types.ts b/packages/apidom-ns-a2a-1/src/media-types.ts new file mode 100644 index 0000000000..cf3be84d12 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/media-types.ts @@ -0,0 +1,47 @@ +import { last } from 'ramda'; +import { MediaTypes } from '@swagger-api/apidom-core'; + +/** + * @public + */ +export type Format = 'generic' | 'json' | 'yaml'; + +/** + * @public + * + * A2A has no IANA-registered media type. The values below follow the + * `application/vnd.{vendor}.{type};version={ver}` convention used by other + * ApiDOM namespaces, with `a2a` as the vendor tag. + */ +export class A2AMediaTypes extends MediaTypes { + filterByFormat(format: Format = 'generic') { + const effectiveFormat = format === 'generic' ? 'a2a;version' : format; + return this.filter((mediaType) => mediaType.includes(effectiveFormat)); + } + + findBy(version = '1.0.0', format: Format = 'generic') { + const search = + format === 'generic' ? `vnd.a2a;version=${version}` : `vnd.a2a+${format};version=${version}`; + const found = this.find((mediaType) => mediaType.includes(search)); + + return found || this.unknownMediaType; + } + + latest(format: Format = 'generic') { + return last(this.filterByFormat(format)) as string; + } +} + +/** + * @public + */ +const mediaTypes = new A2AMediaTypes( + 'application/vnd.a2a;version=1.0.0', + 'application/vnd.a2a+json;version=1.0.0', + 'application/vnd.a2a+yaml;version=1.0.0', + 'application/vnd.a2a;version=1.0.1', + 'application/vnd.a2a+json;version=1.0.1', + 'application/vnd.a2a+yaml;version=1.0.1', +); + +export default mediaTypes; diff --git a/packages/apidom-ns-a2a-1/src/namespace.ts b/packages/apidom-ns-a2a-1/src/namespace.ts new file mode 100644 index 0000000000..71be989beb --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/namespace.ts @@ -0,0 +1,54 @@ +import { NamespacePluginOptions } from '@swagger-api/apidom-core'; + +import AgentCardElement from './elements/AgentCard.ts'; +import AgentCapabilitiesElement from './elements/AgentCapabilities.ts'; +import AgentExtensionElement from './elements/AgentExtension.ts'; +import AgentProviderElement from './elements/AgentProvider.ts'; +import AgentInterfaceElement from './elements/AgentInterface.ts'; +import AgentSkillElement from './elements/AgentSkill.ts'; +import AgentCardSignatureElement from './elements/AgentCardSignature.ts'; +import SecurityRequirementElement from './elements/SecurityRequirement.ts'; +import SecuritySchemeElement from './elements/SecurityScheme.ts'; +import APIKeySecuritySchemeElement from './elements/APIKeySecurityScheme.ts'; +import HTTPAuthSecuritySchemeElement from './elements/HTTPAuthSecurityScheme.ts'; +import MutualTlsSecuritySchemeElement from './elements/MutualTlsSecurityScheme.ts'; +import OAuth2SecuritySchemeElement from './elements/OAuth2SecurityScheme.ts'; +import OpenIdConnectSecuritySchemeElement from './elements/OpenIdConnectSecurityScheme.ts'; +import OAuthFlowsElement from './elements/OAuthFlows.ts'; +import AuthorizationCodeOAuthFlowElement from './elements/AuthorizationCodeOAuthFlow.ts'; +import ClientCredentialsOAuthFlowElement from './elements/ClientCredentialsOAuthFlow.ts'; +import DeviceCodeOAuthFlowElement from './elements/DeviceCodeOAuthFlow.ts'; +import StringListElement from './elements/StringList.ts'; + +/** + * @public + */ +const a2a1 = { + namespace: (options: NamespacePluginOptions) => { + const { base } = options; + + base.register('a2aAgentCard1', AgentCardElement); + base.register('agentCapabilities', AgentCapabilitiesElement); + base.register('agentExtension', AgentExtensionElement); + base.register('agentProvider', AgentProviderElement); + base.register('agentInterface', AgentInterfaceElement); + base.register('agentSkill', AgentSkillElement); + base.register('agentCardSignature', AgentCardSignatureElement); + base.register('securityRequirement', SecurityRequirementElement); + base.register('securityScheme', SecuritySchemeElement); + base.register('apiKeySecurityScheme', APIKeySecuritySchemeElement); + base.register('httpAuthSecurityScheme', HTTPAuthSecuritySchemeElement); + base.register('mutualTlsSecurityScheme', MutualTlsSecuritySchemeElement); + base.register('oauth2SecurityScheme', OAuth2SecuritySchemeElement); + base.register('openIdConnectSecurityScheme', OpenIdConnectSecuritySchemeElement); + base.register('oauthFlows', OAuthFlowsElement); + base.register('authorizationCodeOAuthFlow', AuthorizationCodeOAuthFlowElement); + base.register('clientCredentialsOAuthFlow', ClientCredentialsOAuthFlowElement); + base.register('deviceCodeOAuthFlow', DeviceCodeOAuthFlowElement); + base.register('stringList', StringListElement); + + return base; + }, +}; + +export default a2a1; diff --git a/packages/apidom-ns-a2a-1/src/predicates.ts b/packages/apidom-ns-a2a-1/src/predicates.ts new file mode 100644 index 0000000000..2c08923d01 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/predicates.ts @@ -0,0 +1,378 @@ +import { createPredicate } from '@swagger-api/apidom-core'; + +import AgentCardElement from './elements/AgentCard.ts'; +import AgentCapabilitiesElement from './elements/AgentCapabilities.ts'; +import AgentExtensionElement from './elements/AgentExtension.ts'; +import AgentProviderElement from './elements/AgentProvider.ts'; +import AgentInterfaceElement from './elements/AgentInterface.ts'; +import AgentSkillElement from './elements/AgentSkill.ts'; +import AgentCardSignatureElement from './elements/AgentCardSignature.ts'; +import SecurityRequirementElement from './elements/SecurityRequirement.ts'; +import SecuritySchemeElement from './elements/SecurityScheme.ts'; +import APIKeySecuritySchemeElement from './elements/APIKeySecurityScheme.ts'; +import HTTPAuthSecuritySchemeElement from './elements/HTTPAuthSecurityScheme.ts'; +import MutualTlsSecuritySchemeElement from './elements/MutualTlsSecurityScheme.ts'; +import OAuth2SecuritySchemeElement from './elements/OAuth2SecurityScheme.ts'; +import OpenIdConnectSecuritySchemeElement from './elements/OpenIdConnectSecurityScheme.ts'; +import OAuthFlowsElement from './elements/OAuthFlows.ts'; +import AuthorizationCodeOAuthFlowElement from './elements/AuthorizationCodeOAuthFlow.ts'; +import ClientCredentialsOAuthFlowElement from './elements/ClientCredentialsOAuthFlow.ts'; +import DeviceCodeOAuthFlowElement from './elements/DeviceCodeOAuthFlow.ts'; +import StringListElement from './elements/StringList.ts'; +import SkillsElement from './elements/nces/Skills.ts'; +import SignaturesElement from './elements/nces/Signatures.ts'; +import SupportedInterfacesElement from './elements/nces/SupportedInterfaces.ts'; +import SecurityRequirementsElement from './elements/nces/SecurityRequirements.ts'; +import ExtensionsElement from './elements/nces/Extensions.ts'; +import SecuritySchemesElement from './elements/nces/SecuritySchemes.ts'; + +/** + * @public + */ +export const isAgentCardElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentCardElement => + element instanceof AgentCardElement || + (hasBasicElementProps(element) && + isElementType('a2aAgentCard1', element) && + primitiveEq('object', element) && + hasClass('api', element) && + hasClass('agent-card', element)); + }, +); + +/** + * @public + */ +export const isAgentCapabilitiesElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentCapabilitiesElement => + element instanceof AgentCapabilitiesElement || + (hasBasicElementProps(element) && + isElementType('agentCapabilities', element) && + primitiveEq('object', element) && + hasClass('agent-capabilities', element)); + }, +); + +/** + * @public + */ +export const isAgentExtensionElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentExtensionElement => + element instanceof AgentExtensionElement || + (hasBasicElementProps(element) && + isElementType('agentExtension', element) && + primitiveEq('object', element) && + hasClass('agent-extension', element)); + }, +); + +/** + * @public + */ +export const isAgentProviderElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentProviderElement => + element instanceof AgentProviderElement || + (hasBasicElementProps(element) && + isElementType('agentProvider', element) && + primitiveEq('object', element) && + hasClass('agent-provider', element)); + }, +); + +/** + * @public + */ +export const isAgentInterfaceElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentInterfaceElement => + element instanceof AgentInterfaceElement || + (hasBasicElementProps(element) && + isElementType('agentInterface', element) && + primitiveEq('object', element) && + hasClass('agent-interface', element)); + }, +); + +/** + * @public + */ +export const isAgentSkillElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentSkillElement => + element instanceof AgentSkillElement || + (hasBasicElementProps(element) && + isElementType('agentSkill', element) && + primitiveEq('object', element) && + hasClass('agent-skill', element)); + }, +); + +/** + * @public + */ +export const isAgentCardSignatureElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AgentCardSignatureElement => + element instanceof AgentCardSignatureElement || + (hasBasicElementProps(element) && + isElementType('agentCardSignature', element) && + primitiveEq('object', element) && + hasClass('agent-card-signature', element)); + }, +); + +/** + * @public + */ +export const isSecurityRequirementElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SecurityRequirementElement => + element instanceof SecurityRequirementElement || + (hasBasicElementProps(element) && + isElementType('securityRequirement', element) && + primitiveEq('object', element) && + hasClass('security-requirement', element)); + }, +); + +/** + * @public + */ +export const isSecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SecuritySchemeElement => + element instanceof SecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('securityScheme', element) && + primitiveEq('object', element) && + hasClass('security-scheme', element)); + }, +); + +/** + * @public + */ +export const isAPIKeySecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is APIKeySecuritySchemeElement => + element instanceof APIKeySecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('apiKeySecurityScheme', element) && + primitiveEq('object', element) && + hasClass('api-key-security-scheme', element)); + }, +); + +/** + * @public + */ +export const isHTTPAuthSecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is HTTPAuthSecuritySchemeElement => + element instanceof HTTPAuthSecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('httpAuthSecurityScheme', element) && + primitiveEq('object', element) && + hasClass('http-auth-security-scheme', element)); + }, +); + +/** + * @public + */ +export const isMutualTlsSecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is MutualTlsSecuritySchemeElement => + element instanceof MutualTlsSecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('mutualTlsSecurityScheme', element) && + primitiveEq('object', element) && + hasClass('mutual-tls-security-scheme', element)); + }, +); + +/** + * @public + */ +export const isOAuth2SecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is OAuth2SecuritySchemeElement => + element instanceof OAuth2SecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('oauth2SecurityScheme', element) && + primitiveEq('object', element) && + hasClass('oauth2-security-scheme', element)); + }, +); + +/** + * @public + */ +export const isOpenIdConnectSecuritySchemeElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is OpenIdConnectSecuritySchemeElement => + element instanceof OpenIdConnectSecuritySchemeElement || + (hasBasicElementProps(element) && + isElementType('openIdConnectSecurityScheme', element) && + primitiveEq('object', element) && + hasClass('open-id-connect-security-scheme', element)); + }, +); + +/** + * @public + */ +export const isOAuthFlowsElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is OAuthFlowsElement => + element instanceof OAuthFlowsElement || + (hasBasicElementProps(element) && + isElementType('oauthFlows', element) && + primitiveEq('object', element) && + hasClass('oauth-flows', element)); + }, +); + +/** + * @public + */ +export const isAuthorizationCodeOAuthFlowElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is AuthorizationCodeOAuthFlowElement => + element instanceof AuthorizationCodeOAuthFlowElement || + (hasBasicElementProps(element) && + isElementType('authorizationCodeOAuthFlow', element) && + primitiveEq('object', element) && + hasClass('authorization-code-oauth-flow', element)); + }, +); + +/** + * @public + */ +export const isClientCredentialsOAuthFlowElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is ClientCredentialsOAuthFlowElement => + element instanceof ClientCredentialsOAuthFlowElement || + (hasBasicElementProps(element) && + isElementType('clientCredentialsOAuthFlow', element) && + primitiveEq('object', element) && + hasClass('client-credentials-oauth-flow', element)); + }, +); + +/** + * @public + */ +export const isDeviceCodeOAuthFlowElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is DeviceCodeOAuthFlowElement => + element instanceof DeviceCodeOAuthFlowElement || + (hasBasicElementProps(element) && + isElementType('deviceCodeOAuthFlow', element) && + primitiveEq('object', element) && + hasClass('device-code-oauth-flow', element)); + }, +); + +/** + * @public + */ +export const isStringListElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is StringListElement => + element instanceof StringListElement || + (hasBasicElementProps(element) && + isElementType('stringList', element) && + primitiveEq('object', element) && + hasClass('string-list', element)); + }, +); + +/** + * @public + */ +export const isSkillsElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SkillsElement => + element instanceof SkillsElement || + (hasBasicElementProps(element) && + isElementType('array', element) && + primitiveEq('array', element) && + hasClass('skills', element)); + }, +); + +/** + * @public + */ +export const isSignaturesElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SignaturesElement => + element instanceof SignaturesElement || + (hasBasicElementProps(element) && + isElementType('array', element) && + primitiveEq('array', element) && + hasClass('signatures', element)); + }, +); + +/** + * @public + */ +export const isSupportedInterfacesElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SupportedInterfacesElement => + element instanceof SupportedInterfacesElement || + (hasBasicElementProps(element) && + isElementType('array', element) && + primitiveEq('array', element) && + hasClass('supported-interfaces', element)); + }, +); + +/** + * @public + */ +export const isSecurityRequirementsElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SecurityRequirementsElement => + element instanceof SecurityRequirementsElement || + (hasBasicElementProps(element) && + isElementType('array', element) && + primitiveEq('array', element) && + hasClass('security-requirements', element)); + }, +); + +/** + * @public + */ +export const isExtensionsElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is ExtensionsElement => + element instanceof ExtensionsElement || + (hasBasicElementProps(element) && + isElementType('array', element) && + primitiveEq('array', element) && + hasClass('extensions', element)); + }, +); + +/** + * @public + */ +export const isSecuritySchemesElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => { + return (element: unknown): element is SecuritySchemesElement => + element instanceof SecuritySchemesElement || + (hasBasicElementProps(element) && + isElementType('object', element) && + primitiveEq('object', element) && + hasClass('security-schemes', element)); + }, +); diff --git a/packages/apidom-ns-a2a-1/src/refractor/canonicalize.ts b/packages/apidom-ns-a2a-1/src/refractor/canonicalize.ts new file mode 100644 index 0000000000..982509237f --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/canonicalize.ts @@ -0,0 +1,115 @@ +/** + * Snake_case → camelCase canonicalisation for A2A documents. + * + * A2A's JSON encoding allows both camelCase and snake_case keys because the + * spec is generated from protobuf, where the canonical form is snake_case + * but protobuf's JSON encoding canonicalises to camelCase. Tooling on either + * side of the conversion may emit either form. + * + * This module exposes a single function `canonicalizeKeys` that recursively + * rewrites known snake_case keys to their camelCase equivalents *on an ApiDOM + * element tree*. It runs against the generic ApiDOM produced by `baseRefract`, + * before the namespace visitor pipeline, so downstream visitors only ever see + * camelCase keys. + * + * Operating on the element tree (rather than a plain JS value) is required + * because documents reach the refractor through two paths: callers may pass a + * plain JS value, but the parser adapters pass the already-refracted generic + * Element from `parseJSON` / `parseYAML`. Both converge on a generic Element + * after `baseRefract`, so canonicalising there covers every entry point. + * + * Note: the mapping is global rather than element-scoped. This works because + * every snake_case key listed below appears in only one logical position in + * the schema (e.g. `default_input_modes` appears only inside AgentCard / + * AgentSkill, never in unrelated contexts). If A2A ever introduces an + * ambiguous snake_case key, this module will need a contextual variant. + * + * @public + */ +import { + Element, + MemberElement, + toValue, + isElement, + isObjectElement, + isArrayElement, + isStringElement, +} from '@swagger-api/apidom-core'; + +const SNAKE_TO_CAMEL: Record = { + // AgentCard + default_input_modes: 'defaultInputModes', + default_output_modes: 'defaultOutputModes', + documentation_url: 'documentationUrl', + icon_url: 'iconUrl', + security_requirements: 'securityRequirements', + security_schemes: 'securitySchemes', + supported_interfaces: 'supportedInterfaces', + // AgentCapabilities + extended_agent_card: 'extendedAgentCard', + push_notifications: 'pushNotifications', + // AgentInterface + protocol_binding: 'protocolBinding', + protocol_version: 'protocolVersion', + // AgentSkill + input_modes: 'inputModes', + output_modes: 'outputModes', + // HTTPAuthSecurityScheme + bearer_format: 'bearerFormat', + // OAuth2SecurityScheme + oauth2_metadata_url: 'oauth2MetadataUrl', + // OpenIdConnectSecurityScheme + open_id_connect_url: 'openIdConnectUrl', + // OAuth flow types (Authorization, ClientCredentials, DeviceCode) + authorization_url: 'authorizationUrl', + token_url: 'tokenUrl', + refresh_url: 'refreshUrl', + device_authorization_url: 'deviceAuthorizationUrl', + pkce_required: 'pkceRequired', + // OAuthFlows wrapper + authorization_code: 'authorizationCode', + client_credentials: 'clientCredentials', + device_code: 'deviceCode', + // SecurityScheme wrapper (protobuf oneof subfields) + api_key_security_scheme: 'apiKeySecurityScheme', + http_auth_security_scheme: 'httpAuthSecurityScheme', + mtls_security_scheme: 'mtlsSecurityScheme', + oauth2_security_scheme: 'oauth2SecurityScheme', + open_id_connect_security_scheme: 'openIdConnectSecurityScheme', +}; + +/** + * Recursively rewrite known snake_case member keys to camelCase in an ApiDOM + * element tree. Mutates `element` in place (the generic tree is freshly built + * by `baseRefract`, so in-place rewriting is safe) and returns it. No-op for + * keys not in the mapping table. Key `meta`/`attributes` (e.g. source maps) + * are preserved because only the key's primitive content is rewritten. + * + * @public + */ +export const canonicalizeKeys = (element: Element): Element => { + if (isObjectElement(element)) { + (element.content as unknown as MemberElement[]).forEach((member) => { + const { key, value } = member; + if (isStringElement(key)) { + const canonicalKey = SNAKE_TO_CAMEL[toValue(key) as string]; + if (typeof canonicalKey === 'string') { + // minim's base typing declares `content` as `Array`, but a + // StringElement's content is the string primitive — assign through a + // narrowed target so the rewrite type-checks. + (key as unknown as { content: string }).content = canonicalKey; + } + } + if (isElement(value)) { + canonicalizeKeys(value); + } + }); + } else if (isArrayElement(element)) { + (element.content as unknown as Element[]).forEach((item) => { + canonicalizeKeys(item); + }); + } + return element; +}; + +export default canonicalizeKeys; diff --git a/packages/apidom-ns-a2a-1/src/refractor/index.ts b/packages/apidom-ns-a2a-1/src/refractor/index.ts new file mode 100644 index 0000000000..c20af106f8 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/index.ts @@ -0,0 +1,58 @@ +import { + visit, + Element, + dereference, + refract as baseRefract, + dispatchRefractorPlugins, +} from '@swagger-api/apidom-core'; +import { path } from 'ramda'; + +import type VisitorClass from './visitors/Visitor.ts'; +import specification from './specification.ts'; +import { keyMap, getNodeType } from '../traversal/visitor.ts'; +import createToolbox from './toolbox.ts'; +import { canonicalizeKeys } from './canonicalize.ts'; + +/** + * @public + */ +const refract = ( + value: unknown, + { specPath = ['visitors', 'document', 'objects', 'AgentCard', '$visitor'], plugins = [] } = {}, +): T => { + // Canonicalise snake_case keys → camelCase. Both entry points (a plain JS + // value and the generic Element passed by the parser adapters) converge on + // a generic Element after `baseRefract`, so canonicalising the element tree + // here covers every path before the namespace visitors run. + const element = baseRefract(value); + canonicalizeKeys(element); + const resolvedSpec = dereference(specification); + + /** + * This is where generic ApiDOM becomes semantic (namespace applied). + * We don't allow consumers to hook into this translation. + * Though we allow consumers to define their own plugins on already transformed ApiDOM. + */ + const RootVisitorClass = path(specPath, resolvedSpec) as typeof VisitorClass; + const rootVisitor = new RootVisitorClass({ specObj: resolvedSpec }); + + visit(element, rootVisitor); + + /** + * Running plugins visitors means extra single traversal === performance hit. + */ + return dispatchRefractorPlugins(rootVisitor.element, plugins, { + toolboxCreator: createToolbox, + visitorOptions: { keyMap, nodeTypeGetter: getNodeType }, + }) as T; +}; + +/** + * @public + */ +export const createRefractor = + (specPath: string[]) => + (value: unknown, options = {}) => + refract(value, { specPath, ...options }); + +export default refract; diff --git a/packages/apidom-ns-a2a-1/src/refractor/plugins/replace-empty-element.ts b/packages/apidom-ns-a2a-1/src/refractor/plugins/replace-empty-element.ts new file mode 100644 index 0000000000..ddd79507f3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/plugins/replace-empty-element.ts @@ -0,0 +1,277 @@ +import { + ArrayElement, + ObjectElement, + StringElement, + isStringElement, + isArrayElement, + isElement, + isMemberElement, + includesClasses, + cloneDeep, + toValue, + assignSourceMap, +} from '@swagger-api/apidom-core'; + +/** + * A2A v1 specification elements. + */ +import AgentProviderElement from '../../elements/AgentProvider.ts'; +import AgentCapabilitiesElement from '../../elements/AgentCapabilities.ts'; +import AgentExtensionElement from '../../elements/AgentExtension.ts'; +import AgentInterfaceElement from '../../elements/AgentInterface.ts'; +import AgentSkillElement from '../../elements/AgentSkill.ts'; +import AgentCardSignatureElement from '../../elements/AgentCardSignature.ts'; +import SecurityRequirementElement from '../../elements/SecurityRequirement.ts'; +import SecuritySchemeElement from '../../elements/SecurityScheme.ts'; +import APIKeySecuritySchemeElement from '../../elements/APIKeySecurityScheme.ts'; +import HTTPAuthSecuritySchemeElement from '../../elements/HTTPAuthSecurityScheme.ts'; +import MutualTlsSecuritySchemeElement from '../../elements/MutualTlsSecurityScheme.ts'; +import OAuth2SecuritySchemeElement from '../../elements/OAuth2SecurityScheme.ts'; +import OpenIdConnectSecuritySchemeElement from '../../elements/OpenIdConnectSecurityScheme.ts'; +import OAuthFlowsElement from '../../elements/OAuthFlows.ts'; +import AuthorizationCodeOAuthFlowElement from '../../elements/AuthorizationCodeOAuthFlow.ts'; +import ClientCredentialsOAuthFlowElement from '../../elements/ClientCredentialsOAuthFlow.ts'; +import DeviceCodeOAuthFlowElement from '../../elements/DeviceCodeOAuthFlow.ts'; +// non-concrete Elements (NCEs) +import SkillsElement from '../../elements/nces/Skills.ts'; +import SignaturesElement from '../../elements/nces/Signatures.ts'; +import SupportedInterfacesElement from '../../elements/nces/SupportedInterfaces.ts'; +import SecurityRequirementsElement from '../../elements/nces/SecurityRequirements.ts'; +import ExtensionsElement from '../../elements/nces/Extensions.ts'; +import SecuritySchemesElement from '../../elements/nces/SecuritySchemes.ts'; +import { getNodeType } from '../../traversal/visitor.ts'; + +/** + * This plugin is specific to YAML 1.2 format, which allows defining key-value pairs + * with empty key, empty value, or both. If the value is not provided in YAML format, + * this plugin compensates for this missing value with the most appropriate semantic element type. + * + * https://yaml.org/spec/1.2.2/#72-empty-nodes + * + * @example + * + * ```yaml + * name: 'agent example' + * capabilities: + * ``` + * Refracting result without this plugin: + * + * (A2aAgentCard1Element + * (MemberElement + * (StringElement) + * (StringElement)) + * (MemberElement + * (StringElement) + * (StringElement)) + * + * Refracting result with this plugin: + * + * (A2aAgentCard1Element + * (MemberElement + * (StringElement) + * (StringElement)) + * (MemberElement + * (StringElement) + * (AgentCapabilitiesElement)) + */ + +const isEmptyElement = (element: unknown) => + isStringElement(element) && includesClasses(['yaml-e-node', 'yaml-e-scalar'], element); + +const schema = { + // concrete types handling (CTs) + A2aAgentCard1Element: { + provider(...args: any[]) { + return new AgentProviderElement(...args); + }, + capabilities(...args: any[]) { + return new AgentCapabilitiesElement(...args); + }, + defaultInputModes(...args: any[]) { + return new ArrayElement(...args); + }, + defaultOutputModes(...args: any[]) { + return new ArrayElement(...args); + }, + supportedInterfaces(...args: any[]) { + return new SupportedInterfacesElement(...args); + }, + skills(...args: any[]) { + return new SkillsElement(...args); + }, + securitySchemes(...args: any[]) { + return new SecuritySchemesElement(...args); + }, + securityRequirements(...args: any[]) { + return new SecurityRequirementsElement(...args); + }, + signatures(...args: any[]) { + return new SignaturesElement(...args); + }, + }, + AgentCapabilitiesElement: { + extensions(...args: any[]) { + return new ExtensionsElement(...args); + }, + }, + AgentExtensionElement: { + params(...args: any[]) { + return new ObjectElement(...args); + }, + }, + AgentSkillElement: { + tags(...args: any[]) { + return new ArrayElement(...args); + }, + examples(...args: any[]) { + return new ArrayElement(...args); + }, + inputModes(...args: any[]) { + return new ArrayElement(...args); + }, + outputModes(...args: any[]) { + return new ArrayElement(...args); + }, + securityRequirements(...args: any[]) { + return new SecurityRequirementsElement(...args); + }, + }, + SecuritySchemeElement: { + apiKeySecurityScheme(...args: any[]) { + return new APIKeySecuritySchemeElement(...args); + }, + httpAuthSecurityScheme(...args: any[]) { + return new HTTPAuthSecuritySchemeElement(...args); + }, + mtlsSecurityScheme(...args: any[]) { + return new MutualTlsSecuritySchemeElement(...args); + }, + oauth2SecurityScheme(...args: any[]) { + return new OAuth2SecuritySchemeElement(...args); + }, + openIdConnectSecurityScheme(...args: any[]) { + return new OpenIdConnectSecuritySchemeElement(...args); + }, + }, + Oauth2SecuritySchemeElement: { + flows(...args: any[]) { + return new OAuthFlowsElement(...args); + }, + }, + OauthFlowsElement: { + authorizationCode(...args: any[]) { + return new AuthorizationCodeOAuthFlowElement(...args); + }, + clientCredentials(...args: any[]) { + return new ClientCredentialsOAuthFlowElement(...args); + }, + deviceCode(...args: any[]) { + return new DeviceCodeOAuthFlowElement(...args); + }, + }, + AuthorizationCodeOAuthFlowElement: { + scopes(...args: any[]) { + return new ObjectElement(...args); + }, + }, + ClientCredentialsOAuthFlowElement: { + scopes(...args: any[]) { + return new ObjectElement(...args); + }, + }, + DeviceCodeOAuthFlowElement: { + scopes(...args: any[]) { + return new ObjectElement(...args); + }, + }, + SecurityRequirementElement: { + schemes(...args: any[]) { + return new ObjectElement(...args); + }, + }, + // non-concrete types handling (NCEs) + [SkillsElement.primaryClass]: { + '<*>': function asterisk(...args: any[]) { + return new AgentSkillElement(...args); + }, + }, + [SignaturesElement.primaryClass]: { + '<*>': function asterisk(...args: any[]) { + return new AgentCardSignatureElement(...args); + }, + }, + [SupportedInterfacesElement.primaryClass]: { + '<*>': function asterisk(...args: any[]) { + return new AgentInterfaceElement(...args); + }, + }, + [SecurityRequirementsElement.primaryClass]: { + '<*>': function asterisk(...args: any[]) { + return new SecurityRequirementElement(...args); + }, + }, + [ExtensionsElement.primaryClass]: { + '<*>': function asterisk(...args: any[]) { + return new AgentExtensionElement(...args); + }, + }, + [SecuritySchemesElement.primaryClass]: { + '[key: *]': function key(...args: any[]) { + return new SecuritySchemeElement(...args); + }, + }, + 'security-requirement-schemes': { + '[key: *]': function key(...args: any[]) { + return new ArrayElement(...args); + }, + }, +}; + +const findElementFactory = (ancestor: any, keyName: string) => { + const elementType = getNodeType(ancestor); // @ts-ignore + const keyMapping = schema[elementType] || schema[toValue(ancestor.classes.first)]; + + return typeof keyMapping === 'undefined' + ? undefined + : Object.prototype.hasOwnProperty.call(keyMapping, '[key: *]') + ? keyMapping['[key: *]'] + : keyMapping[keyName]; +}; + +/** + * @public + */ +const plugin = () => () => ({ + visitor: { + StringElement(element: StringElement, key: any, parent: any, path: any, ancestors: any[]) { + if (!isEmptyElement(element)) return undefined; + + const lineage = [...ancestors, parent].filter(isElement); + const parentElement = lineage[lineage.length - 1]; // @TODO(vladimir.gorej@gmail.com): can be replaced by Array.prototype.at in future + let elementFactory; + let context; + + if (isArrayElement(parentElement)) { + context = element; + elementFactory = findElementFactory(parentElement, '<*>'); + } else if (isMemberElement(parentElement)) { + context = lineage[lineage.length - 2]; // @TODO(vladimir.gorej@gmail.com): can be replaced by Array.prototype.at in future + elementFactory = findElementFactory(context, toValue(parentElement.key)); + } + + // no element factory found + if (typeof elementFactory !== 'function') return undefined; + + const result = elementFactory.call( + { context }, + undefined, + cloneDeep(element.meta), + cloneDeep(element.attributes), + ); + + return assignSourceMap(result, element); + }, + }, +}); + +export default plugin; diff --git a/packages/apidom-ns-a2a-1/src/refractor/predicates.ts b/packages/apidom-ns-a2a-1/src/refractor/predicates.ts new file mode 100644 index 0000000000..ad183bcd8e --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/predicates.ts @@ -0,0 +1,23 @@ +import { startsWith } from 'ramda'; +import { + MemberElement, + ObjectElement, + isStringElement, + toValue, + isObjectElement, +} from '@swagger-api/apidom-core'; + +export interface ReferenceLikeElement extends ObjectElement { + hasKey: (value: '$ref') => true; +} + +/** + * @public + */ +export const isA2ASpecificationExtension = (element: MemberElement): boolean => { + return isStringElement(element.key) && startsWith('x-', toValue(element.key)); +}; + +export const isReferenceLikeElement = (element: unknown): element is ReferenceLikeElement => { + return isObjectElement(element) && element.hasKey('$ref'); +}; diff --git a/packages/apidom-ns-a2a-1/src/refractor/registration.ts b/packages/apidom-ns-a2a-1/src/refractor/registration.ts new file mode 100644 index 0000000000..9a8acea549 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/registration.ts @@ -0,0 +1,176 @@ +import AgentCardElement from '../elements/AgentCard.ts'; +import AgentCapabilitiesElement from '../elements/AgentCapabilities.ts'; +import AgentExtensionElement from '../elements/AgentExtension.ts'; +import AgentProviderElement from '../elements/AgentProvider.ts'; +import AgentInterfaceElement from '../elements/AgentInterface.ts'; +import AgentSkillElement from '../elements/AgentSkill.ts'; +import AgentCardSignatureElement from '../elements/AgentCardSignature.ts'; +import SecurityRequirementElement from '../elements/SecurityRequirement.ts'; +import SecuritySchemeElement from '../elements/SecurityScheme.ts'; +import APIKeySecuritySchemeElement from '../elements/APIKeySecurityScheme.ts'; +import HTTPAuthSecuritySchemeElement from '../elements/HTTPAuthSecurityScheme.ts'; +import MutualTlsSecuritySchemeElement from '../elements/MutualTlsSecurityScheme.ts'; +import OAuth2SecuritySchemeElement from '../elements/OAuth2SecurityScheme.ts'; +import OpenIdConnectSecuritySchemeElement from '../elements/OpenIdConnectSecurityScheme.ts'; +import OAuthFlowsElement from '../elements/OAuthFlows.ts'; +import AuthorizationCodeOAuthFlowElement from '../elements/AuthorizationCodeOAuthFlow.ts'; +import ClientCredentialsOAuthFlowElement from '../elements/ClientCredentialsOAuthFlow.ts'; +import DeviceCodeOAuthFlowElement from '../elements/DeviceCodeOAuthFlow.ts'; +import StringListElement from '../elements/StringList.ts'; +import { createRefractor } from './index.ts'; + +AgentCardElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentCard', + '$visitor', +]); +AgentCapabilitiesElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentCapabilities', + '$visitor', +]); +AgentExtensionElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentExtension', + '$visitor', +]); +AgentProviderElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentProvider', + '$visitor', +]); +AgentInterfaceElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentInterface', + '$visitor', +]); +AgentSkillElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentSkill', + '$visitor', +]); +AgentCardSignatureElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AgentCardSignature', + '$visitor', +]); +SecurityRequirementElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'SecurityRequirement', + '$visitor', +]); +SecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'SecurityScheme', + '$visitor', +]); +APIKeySecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'APIKeySecurityScheme', + '$visitor', +]); +HTTPAuthSecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'HTTPAuthSecurityScheme', + '$visitor', +]); +MutualTlsSecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'MutualTlsSecurityScheme', + '$visitor', +]); +OAuth2SecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'OAuth2SecurityScheme', + '$visitor', +]); +OpenIdConnectSecuritySchemeElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'OpenIdConnectSecurityScheme', + '$visitor', +]); +OAuthFlowsElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'OAuthFlows', + '$visitor', +]); +AuthorizationCodeOAuthFlowElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'AuthorizationCodeOAuthFlow', + '$visitor', +]); +ClientCredentialsOAuthFlowElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'ClientCredentialsOAuthFlow', + '$visitor', +]); +DeviceCodeOAuthFlowElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'DeviceCodeOAuthFlow', + '$visitor', +]); +StringListElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'StringList', + '$visitor', +]); + +export { + AgentCardElement, + AgentCapabilitiesElement, + AgentExtensionElement, + AgentProviderElement, + AgentInterfaceElement, + AgentSkillElement, + AgentCardSignatureElement, + SecurityRequirementElement, + SecuritySchemeElement, + APIKeySecuritySchemeElement, + HTTPAuthSecuritySchemeElement, + MutualTlsSecuritySchemeElement, + OAuth2SecuritySchemeElement, + OpenIdConnectSecuritySchemeElement, + OAuthFlowsElement, + AuthorizationCodeOAuthFlowElement, + ClientCredentialsOAuthFlowElement, + DeviceCodeOAuthFlowElement, + StringListElement, +}; diff --git a/packages/apidom-ns-a2a-1/src/refractor/specification.ts b/packages/apidom-ns-a2a-1/src/refractor/specification.ts new file mode 100644 index 0000000000..647c6c3194 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/specification.ts @@ -0,0 +1,232 @@ +import AgentCardVisitor from './visitors/a2a-1/agent-card/index.ts'; +import AgentCapabilitiesVisitor from './visitors/a2a-1/agent-capabilities/index.ts'; +import AgentExtensionVisitor from './visitors/a2a-1/agent-extension/index.ts'; +import AgentProviderVisitor from './visitors/a2a-1/agent-provider/index.ts'; +import AgentInterfaceVisitor from './visitors/a2a-1/agent-interface/index.ts'; +import AgentSkillVisitor from './visitors/a2a-1/agent-skill/index.ts'; +import AgentCardSignatureVisitor from './visitors/a2a-1/agent-card-signature/index.ts'; +import SecurityRequirementVisitor from './visitors/a2a-1/security-requirement/index.ts'; +import SecuritySchemeVisitor from './visitors/a2a-1/security-scheme/index.ts'; +import APIKeySecuritySchemeVisitor from './visitors/a2a-1/api-key-security-scheme/index.ts'; +import HTTPAuthSecuritySchemeVisitor from './visitors/a2a-1/http-auth-security-scheme/index.ts'; +import MutualTlsSecuritySchemeVisitor from './visitors/a2a-1/mutual-tls-security-scheme/index.ts'; +import OAuth2SecuritySchemeVisitor from './visitors/a2a-1/oauth2-security-scheme/index.ts'; +import OpenIdConnectSecuritySchemeVisitor from './visitors/a2a-1/open-id-connect-security-scheme/index.ts'; +import OAuthFlowsVisitor from './visitors/a2a-1/oauth-flows/index.ts'; +import AuthorizationCodeOAuthFlowVisitor from './visitors/a2a-1/authorization-code-oauth-flow/index.ts'; +import ClientCredentialsOAuthFlowVisitor from './visitors/a2a-1/client-credentials-oauth-flow/index.ts'; +import DeviceCodeOAuthFlowVisitor from './visitors/a2a-1/device-code-oauth-flow/index.ts'; +import StringListVisitor from './visitors/a2a-1/string-list/index.ts'; +import SkillsVisitor from './visitors/a2a-1/SkillsVisitor.ts'; +import SignaturesVisitor from './visitors/a2a-1/SignaturesVisitor.ts'; +import SupportedInterfacesVisitor from './visitors/a2a-1/SupportedInterfacesVisitor.ts'; +import SecurityRequirementsVisitor from './visitors/a2a-1/SecurityRequirementsVisitor.ts'; +import ExtensionsVisitor from './visitors/a2a-1/ExtensionsVisitor.ts'; +import SecuritySchemesVisitor from './visitors/a2a-1/SecuritySchemesVisitor.ts'; +import SecurityRequirementSchemesVisitor from './visitors/a2a-1/security-requirement/SchemesVisitor.ts'; +import FallbackVisitor from './visitors/FallbackVisitor.ts'; + +/** + * Specification object allows us to have complete control over visitors + * when traversing the ApiDOM. + * Specification also allows us to create amended refractors from + * existing ones by manipulating it. + * + * Note: Specification object allows to use absolute internal JSON pointers. + * + * @public + * + * A2A's JSON encoding allows both camelCase and snake_case property names + * (a protobuf JSON convention). Snake_case canonicalisation is handled by + * `refractor/canonicalize.ts`, which runs before refraction — by the time + * the visitors here see the input, all known snake_case keys have been + * rewritten to camelCase. The fixed-field map below only needs camelCase + * entries. + */ +const specification = { + visitors: { + value: FallbackVisitor, + document: { + objects: { + AgentCard: { + $visitor: AgentCardVisitor, + fixedFields: { + name: { $ref: '#/visitors/value' }, + description: { $ref: '#/visitors/value' }, + version: { $ref: '#/visitors/value' }, + iconUrl: { $ref: '#/visitors/value' }, + documentationUrl: { $ref: '#/visitors/value' }, + provider: { $ref: '#/visitors/document/objects/AgentProvider' }, + capabilities: { $ref: '#/visitors/document/objects/AgentCapabilities' }, + defaultInputModes: { $ref: '#/visitors/value' }, + defaultOutputModes: { $ref: '#/visitors/value' }, + supportedInterfaces: SupportedInterfacesVisitor, + skills: SkillsVisitor, + securitySchemes: SecuritySchemesVisitor, + securityRequirements: SecurityRequirementsVisitor, + signatures: SignaturesVisitor, + }, + }, + AgentCapabilities: { + $visitor: AgentCapabilitiesVisitor, + fixedFields: { + streaming: { $ref: '#/visitors/value' }, + pushNotifications: { $ref: '#/visitors/value' }, + extendedAgentCard: { $ref: '#/visitors/value' }, + extensions: ExtensionsVisitor, + }, + }, + AgentExtension: { + $visitor: AgentExtensionVisitor, + fixedFields: { + uri: { $ref: '#/visitors/value' }, + description: { $ref: '#/visitors/value' }, + required: { $ref: '#/visitors/value' }, + params: { $ref: '#/visitors/value' }, + }, + }, + AgentProvider: { + $visitor: AgentProviderVisitor, + fixedFields: { + organization: { $ref: '#/visitors/value' }, + url: { $ref: '#/visitors/value' }, + }, + }, + AgentInterface: { + $visitor: AgentInterfaceVisitor, + fixedFields: { + url: { $ref: '#/visitors/value' }, + protocolBinding: { $ref: '#/visitors/value' }, + protocolVersion: { $ref: '#/visitors/value' }, + tenant: { $ref: '#/visitors/value' }, + }, + }, + AgentSkill: { + $visitor: AgentSkillVisitor, + fixedFields: { + id: { $ref: '#/visitors/value' }, + name: { $ref: '#/visitors/value' }, + description: { $ref: '#/visitors/value' }, + tags: { $ref: '#/visitors/value' }, + examples: { $ref: '#/visitors/value' }, + inputModes: { $ref: '#/visitors/value' }, + outputModes: { $ref: '#/visitors/value' }, + securityRequirements: SecurityRequirementsVisitor, + }, + }, + AgentCardSignature: { + $visitor: AgentCardSignatureVisitor, + fixedFields: { + protected: { $ref: '#/visitors/value' }, + signature: { $ref: '#/visitors/value' }, + header: { $ref: '#/visitors/value' }, + }, + }, + SecurityRequirement: { + $visitor: SecurityRequirementVisitor, + fixedFields: { + schemes: SecurityRequirementSchemesVisitor, + }, + }, + SecurityScheme: { + $visitor: SecuritySchemeVisitor, + fixedFields: { + apiKeySecurityScheme: { $ref: '#/visitors/document/objects/APIKeySecurityScheme' }, + httpAuthSecurityScheme: { $ref: '#/visitors/document/objects/HTTPAuthSecurityScheme' }, + mtlsSecurityScheme: { $ref: '#/visitors/document/objects/MutualTlsSecurityScheme' }, + oauth2SecurityScheme: { $ref: '#/visitors/document/objects/OAuth2SecurityScheme' }, + openIdConnectSecurityScheme: { + $ref: '#/visitors/document/objects/OpenIdConnectSecurityScheme', + }, + }, + }, + APIKeySecurityScheme: { + $visitor: APIKeySecuritySchemeVisitor, + fixedFields: { + description: { $ref: '#/visitors/value' }, + name: { $ref: '#/visitors/value' }, + location: { $ref: '#/visitors/value' }, + }, + }, + HTTPAuthSecurityScheme: { + $visitor: HTTPAuthSecuritySchemeVisitor, + fixedFields: { + description: { $ref: '#/visitors/value' }, + scheme: { $ref: '#/visitors/value' }, + bearerFormat: { $ref: '#/visitors/value' }, + }, + }, + MutualTlsSecurityScheme: { + $visitor: MutualTlsSecuritySchemeVisitor, + fixedFields: { + description: { $ref: '#/visitors/value' }, + }, + }, + OAuth2SecurityScheme: { + $visitor: OAuth2SecuritySchemeVisitor, + fixedFields: { + description: { $ref: '#/visitors/value' }, + flows: { $ref: '#/visitors/document/objects/OAuthFlows' }, + oauth2MetadataUrl: { $ref: '#/visitors/value' }, + }, + }, + OpenIdConnectSecurityScheme: { + $visitor: OpenIdConnectSecuritySchemeVisitor, + fixedFields: { + description: { $ref: '#/visitors/value' }, + openIdConnectUrl: { $ref: '#/visitors/value' }, + }, + }, + OAuthFlows: { + $visitor: OAuthFlowsVisitor, + fixedFields: { + authorizationCode: { + $ref: '#/visitors/document/objects/AuthorizationCodeOAuthFlow', + }, + clientCredentials: { + $ref: '#/visitors/document/objects/ClientCredentialsOAuthFlow', + }, + deviceCode: { $ref: '#/visitors/document/objects/DeviceCodeOAuthFlow' }, + }, + }, + AuthorizationCodeOAuthFlow: { + $visitor: AuthorizationCodeOAuthFlowVisitor, + fixedFields: { + authorizationUrl: { $ref: '#/visitors/value' }, + tokenUrl: { $ref: '#/visitors/value' }, + refreshUrl: { $ref: '#/visitors/value' }, + pkceRequired: { $ref: '#/visitors/value' }, + scopes: { $ref: '#/visitors/value' }, + }, + }, + ClientCredentialsOAuthFlow: { + $visitor: ClientCredentialsOAuthFlowVisitor, + fixedFields: { + tokenUrl: { $ref: '#/visitors/value' }, + refreshUrl: { $ref: '#/visitors/value' }, + scopes: { $ref: '#/visitors/value' }, + }, + }, + DeviceCodeOAuthFlow: { + $visitor: DeviceCodeOAuthFlowVisitor, + fixedFields: { + deviceAuthorizationUrl: { $ref: '#/visitors/value' }, + tokenUrl: { $ref: '#/visitors/value' }, + refreshUrl: { $ref: '#/visitors/value' }, + scopes: { $ref: '#/visitors/value' }, + }, + }, + StringList: { + $visitor: StringListVisitor, + fixedFields: { + list: { $ref: '#/visitors/value' }, + }, + }, + }, + // A2A does not support `x-*` specification extensions (every object + // declares `additionalProperties: false`). No extension visitor block. + }, + }, +} as const; + +export default specification; diff --git a/packages/apidom-ns-a2a-1/src/refractor/toolbox.ts b/packages/apidom-ns-a2a-1/src/refractor/toolbox.ts new file mode 100644 index 0000000000..c5033ec026 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/toolbox.ts @@ -0,0 +1,18 @@ +import { createNamespace, isStringElement } from '@swagger-api/apidom-core'; + +import * as a2aPredicates from '../predicates.ts'; +import * as refractorPredicates from './predicates.ts'; +import a2aNamespace from '../namespace.ts'; + +const createToolbox = () => { + const namespace = createNamespace(a2aNamespace); + const predicates = { + ...refractorPredicates, + ...a2aPredicates, + isStringElement, + }; + + return { predicates, namespace }; +}; + +export default createToolbox; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/FallbackVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/FallbackVisitor.ts new file mode 100644 index 0000000000..2b3f9ab46c --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/FallbackVisitor.ts @@ -0,0 +1,24 @@ +import { Element, BREAK, cloneDeep } from '@swagger-api/apidom-core'; + +import Visitor, { VisitorOptions } from './Visitor.ts'; + +/** + * This visitor is responsible for falling back to current traversed element. + * Given ArazzoSpecificationVisitor expects ObjectElement to be traversed. If + * different Element is provided FallBackVisitor is responsible to assigning + * this Element as current element. + */ + +export type { VisitorOptions as FallbackVisitorOptions }; + +/** + * @public + */ +class FallbackVisitor extends Visitor { + enter(element: Element) { + this.element = cloneDeep(element); + return BREAK; + } +} + +export default FallbackVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationExtensionVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationExtensionVisitor.ts new file mode 100644 index 0000000000..c00bdefc09 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationExtensionVisitor.ts @@ -0,0 +1,21 @@ +import { MemberElement, BREAK, cloneDeep } from '@swagger-api/apidom-core'; + +import SpecificationVisitor, { SpecificationVisitorOptions } from './SpecificationVisitor.ts'; + +export type { SpecificationVisitorOptions as SpecificationExtensionVisitorOptions }; + +/** + * @public + */ +class SpecificationExtensionVisitor extends SpecificationVisitor { + declare public element: MemberElement; + + MemberElement(memberElement: MemberElement) { + this.element = cloneDeep(memberElement); + this.element.classes.push('specification-extension'); + + return BREAK; + } +} + +export default SpecificationExtensionVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationVisitor.ts new file mode 100644 index 0000000000..80942b32af --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/SpecificationVisitor.ts @@ -0,0 +1,79 @@ +import { pathSatisfies, path, pick } from 'ramda'; +import { isFunction } from 'ramda-adjunct'; +import { visit, cloneDeep, Element } from '@swagger-api/apidom-core'; + +import Visitor, { VisitorOptions } from './Visitor.ts'; +import FallbackVisitor from './FallbackVisitor.ts'; +import type specification from '../specification.ts'; + +/** + * This is a base Type for every visitor that does + * internal look-ups to retrieve other child visitors. + * @public + */ +export interface SpecificationVisitorOptions extends VisitorOptions { + readonly specObj: typeof specification; +} + +/** + * @public + */ +class SpecificationVisitor extends Visitor { + protected readonly specObj: typeof specification; + + protected readonly passingOptionsNames = ['specObj']; + + constructor({ specObj, ...rest }: SpecificationVisitorOptions) { + super({ ...rest }); + this.specObj = specObj; + } + + retrievePassingOptions() { + return pick(this.passingOptionsNames as (keyof this)[], this); + } + + retrieveFixedFields(specPath: string[]) { + const fixedFields = path(['visitors', ...specPath, 'fixedFields'], this.specObj); + if (typeof fixedFields === 'object' && fixedFields !== null) { + return Object.keys(fixedFields); + } + return []; + } + + retrieveVisitor(specPath: string[]) { + if (pathSatisfies(isFunction, ['visitors', ...specPath], this.specObj)) { + return path(['visitors', ...specPath], this.specObj); + } + + return path(['visitors', ...specPath, '$visitor'], this.specObj); + } + + retrieveVisitorInstance(specPath: string[], options = {}): Visitor { + const passingOpts = this.retrievePassingOptions(); + const VisitorClz = this.retrieveVisitor(specPath) as typeof Visitor; + const visitorOpts = { ...passingOpts, ...options }; + + return new VisitorClz(visitorOpts); + } + + toRefractedElement(specPath: string[], element: Element, options = {}) { + /** + * This is `Visitor shortcut`: mechanism for short-circuiting the traversal and replacing + * it by basic node cloning. + * + * Visiting the element is equivalent to cloning it if the prototype of a visitor + * is the same as the prototype of FallbackVisitor. If that's the case, we can avoid + * bootstrapping the traversal cycle for fields that don't require any special visiting. + */ + const visitor = this.retrieveVisitorInstance(specPath, options); + + if (visitor instanceof FallbackVisitor && visitor?.constructor === FallbackVisitor) { + return cloneDeep(element); + } + + visit(element, visitor, options); + return visitor.element; + } +} + +export default SpecificationVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/Visitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/Visitor.ts new file mode 100644 index 0000000000..31375ad09a --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/Visitor.ts @@ -0,0 +1,39 @@ +import { + Element, + ObjectElement, + assignSourceMap, + deepmerge, + hasElementSourceMap, +} from '@swagger-api/apidom-core'; + +/** + * @public + */ +export interface VisitorOptions {} + +/** + * @public + */ +class Visitor { + public element!: Element; + + constructor(options: VisitorOptions = {}) { + Object.assign(this, options); + } + + /* eslint-disable class-methods-use-this, no-param-reassign */ + public copyMetaAndAttributes(from: Element, to: Element) { + if (from.meta.length > 0 || to.meta.length > 0) { + to.meta = deepmerge(to.meta, from.meta) as ObjectElement; + } + if (hasElementSourceMap(from)) { + assignSourceMap(to, from); + } + if (from.attributes.length > 0 || from.meta.length > 0) { + to.attributes = deepmerge(to.attributes, from.attributes) as ObjectElement; // eslint-disable-line no-param-reassign + } + } + /* eslint-enable class-methods-use-this, no-param-reassign */ +} + +export default Visitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/ExtensionsVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/ExtensionsVisitor.ts new file mode 100644 index 0000000000..76217a4212 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/ExtensionsVisitor.ts @@ -0,0 +1,40 @@ +import { Mixin } from 'ts-mixer'; +import { ArrayElement, Element, BREAK } from '@swagger-api/apidom-core'; + +import ExtensionsElement from '../../../elements/nces/Extensions.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface ExtensionsVisitorOptions + extends SpecificationVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class ExtensionsVisitor extends Mixin(SpecificationVisitor, FallbackVisitor) { + public readonly element: ExtensionsElement; + + constructor(options: ExtensionsVisitorOptions) { + super(options); + this.element = new ExtensionsElement(); + } + + ArrayElement(arrayElement: ArrayElement) { + arrayElement.forEach((item: Element): void => { + const specPath = ['document', 'objects', 'AgentExtension']; + const element = this.toRefractedElement(specPath, item); + + this.element.push(element); + }); + + this.copyMetaAndAttributes(arrayElement, this.element); + + return BREAK; + } +} + +export default ExtensionsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecurityRequirementsVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecurityRequirementsVisitor.ts new file mode 100644 index 0000000000..6738224b90 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecurityRequirementsVisitor.ts @@ -0,0 +1,40 @@ +import { Mixin } from 'ts-mixer'; +import { ArrayElement, Element, BREAK } from '@swagger-api/apidom-core'; + +import SecurityRequirementsElement from '../../../elements/nces/SecurityRequirements.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SecurityRequirementsVisitorOptions + extends SpecificationVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SecurityRequirementsVisitor extends Mixin(SpecificationVisitor, FallbackVisitor) { + public readonly element: SecurityRequirementsElement; + + constructor(options: SecurityRequirementsVisitorOptions) { + super(options); + this.element = new SecurityRequirementsElement(); + } + + ArrayElement(arrayElement: ArrayElement) { + arrayElement.forEach((item: Element): void => { + const specPath = ['document', 'objects', 'SecurityRequirement']; + const element = this.toRefractedElement(specPath, item); + + this.element.push(element); + }); + + this.copyMetaAndAttributes(arrayElement, this.element); + + return BREAK; + } +} + +export default SecurityRequirementsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecuritySchemesVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecuritySchemesVisitor.ts new file mode 100644 index 0000000000..c429c82a8b --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SecuritySchemesVisitor.ts @@ -0,0 +1,28 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import SecuritySchemesElement from '../../../elements/nces/SecuritySchemes.ts'; +import MapVisitor, { MapVisitorOptions, SpecPath } from '../generics/MapVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SecuritySchemesVisitorOptions extends MapVisitorOptions, FallbackVisitorOptions {} + +/** + * @public + */ +class SecuritySchemesVisitor extends Mixin(MapVisitor, FallbackVisitor) { + public readonly element: SecuritySchemesElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'SecurityScheme']>; + + constructor(options: SecuritySchemesVisitorOptions) { + super(options); + this.element = new SecuritySchemesElement(); + this.specPath = always(['document', 'objects', 'SecurityScheme']); + } +} + +export default SecuritySchemesVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SignaturesVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SignaturesVisitor.ts new file mode 100644 index 0000000000..511e3639e7 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SignaturesVisitor.ts @@ -0,0 +1,40 @@ +import { Mixin } from 'ts-mixer'; +import { ArrayElement, Element, BREAK } from '@swagger-api/apidom-core'; + +import SignaturesElement from '../../../elements/nces/Signatures.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SignaturesVisitorOptions + extends SpecificationVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SignaturesVisitor extends Mixin(SpecificationVisitor, FallbackVisitor) { + public readonly element: SignaturesElement; + + constructor(options: SignaturesVisitorOptions) { + super(options); + this.element = new SignaturesElement(); + } + + ArrayElement(arrayElement: ArrayElement) { + arrayElement.forEach((item: Element): void => { + const specPath = ['document', 'objects', 'AgentCardSignature']; + const element = this.toRefractedElement(specPath, item); + + this.element.push(element); + }); + + this.copyMetaAndAttributes(arrayElement, this.element); + + return BREAK; + } +} + +export default SignaturesVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SkillsVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SkillsVisitor.ts new file mode 100644 index 0000000000..7fcdbe0a9f --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SkillsVisitor.ts @@ -0,0 +1,38 @@ +import { Mixin } from 'ts-mixer'; +import { ArrayElement, Element, BREAK } from '@swagger-api/apidom-core'; + +import SkillsElement from '../../../elements/nces/Skills.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SkillsVisitorOptions extends SpecificationVisitorOptions, FallbackVisitorOptions {} + +/** + * @public + */ +class SkillsVisitor extends Mixin(SpecificationVisitor, FallbackVisitor) { + public readonly element: SkillsElement; + + constructor(options: SkillsVisitorOptions) { + super(options); + this.element = new SkillsElement(); + } + + ArrayElement(arrayElement: ArrayElement) { + arrayElement.forEach((item: Element): void => { + const specPath = ['document', 'objects', 'AgentSkill']; + const element = this.toRefractedElement(specPath, item); + + this.element.push(element); + }); + + this.copyMetaAndAttributes(arrayElement, this.element); + + return BREAK; + } +} + +export default SkillsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SupportedInterfacesVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SupportedInterfacesVisitor.ts new file mode 100644 index 0000000000..6267823108 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/SupportedInterfacesVisitor.ts @@ -0,0 +1,40 @@ +import { Mixin } from 'ts-mixer'; +import { ArrayElement, Element, BREAK } from '@swagger-api/apidom-core'; + +import SupportedInterfacesElement from '../../../elements/nces/SupportedInterfaces.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SupportedInterfacesVisitorOptions + extends SpecificationVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SupportedInterfacesVisitor extends Mixin(SpecificationVisitor, FallbackVisitor) { + public readonly element: SupportedInterfacesElement; + + constructor(options: SupportedInterfacesVisitorOptions) { + super(options); + this.element = new SupportedInterfacesElement(); + } + + ArrayElement(arrayElement: ArrayElement) { + arrayElement.forEach((item: Element): void => { + const specPath = ['document', 'objects', 'AgentInterface']; + const element = this.toRefractedElement(specPath, item); + + this.element.push(element); + }); + + this.copyMetaAndAttributes(arrayElement, this.element); + + return BREAK; + } +} + +export default SupportedInterfacesVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-capabilities/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-capabilities/index.ts new file mode 100644 index 0000000000..fb181c0ffa --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-capabilities/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentCapabilitiesElement from '../../../../elements/AgentCapabilities.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentCapabilitiesVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentCapabilitiesVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentCapabilitiesElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentCapabilities']>; + + constructor(options: AgentCapabilitiesVisitorOptions) { + super(options); + this.element = new AgentCapabilitiesElement(); + this.specPath = always(['document', 'objects', 'AgentCapabilities']); + } +} + +export default AgentCapabilitiesVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card-signature/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card-signature/index.ts new file mode 100644 index 0000000000..d77223b25d --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card-signature/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentCardSignatureElement from '../../../../elements/AgentCardSignature.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentCardSignatureVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentCardSignatureVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentCardSignatureElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentCardSignature']>; + + constructor(options: AgentCardSignatureVisitorOptions) { + super(options); + this.element = new AgentCardSignatureElement(); + this.specPath = always(['document', 'objects', 'AgentCardSignature']); + } +} + +export default AgentCardSignatureVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card/index.ts new file mode 100644 index 0000000000..2c08eb5358 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-card/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentCardElement from '../../../../elements/AgentCard.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentCardVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentCardVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentCardElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentCard']>; + + constructor(options: AgentCardVisitorOptions) { + super(options); + this.element = new AgentCardElement(); + this.specPath = always(['document', 'objects', 'AgentCard']); + } +} + +export default AgentCardVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-extension/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-extension/index.ts new file mode 100644 index 0000000000..9200e210f9 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-extension/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentExtensionElement from '../../../../elements/AgentExtension.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentExtensionVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentExtensionVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentExtensionElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentExtension']>; + + constructor(options: AgentExtensionVisitorOptions) { + super(options); + this.element = new AgentExtensionElement(); + this.specPath = always(['document', 'objects', 'AgentExtension']); + } +} + +export default AgentExtensionVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-interface/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-interface/index.ts new file mode 100644 index 0000000000..c2a6297b54 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-interface/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentInterfaceElement from '../../../../elements/AgentInterface.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentInterfaceVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentInterfaceVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentInterfaceElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentInterface']>; + + constructor(options: AgentInterfaceVisitorOptions) { + super(options); + this.element = new AgentInterfaceElement(); + this.specPath = always(['document', 'objects', 'AgentInterface']); + } +} + +export default AgentInterfaceVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-provider/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-provider/index.ts new file mode 100644 index 0000000000..7e291076a0 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-provider/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentProviderElement from '../../../../elements/AgentProvider.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentProviderVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentProviderVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentProviderElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentProvider']>; + + constructor(options: AgentProviderVisitorOptions) { + super(options); + this.element = new AgentProviderElement(); + this.specPath = always(['document', 'objects', 'AgentProvider']); + } +} + +export default AgentProviderVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-skill/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-skill/index.ts new file mode 100644 index 0000000000..09597647a3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/agent-skill/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AgentSkillElement from '../../../../elements/AgentSkill.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AgentSkillVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AgentSkillVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AgentSkillElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'AgentSkill']>; + + constructor(options: AgentSkillVisitorOptions) { + super(options); + this.element = new AgentSkillElement(); + this.specPath = always(['document', 'objects', 'AgentSkill']); + } +} + +export default AgentSkillVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/api-key-security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/api-key-security-scheme/index.ts new file mode 100644 index 0000000000..9cb7d74fbd --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/api-key-security-scheme/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import APIKeySecuritySchemeElement from '../../../../elements/APIKeySecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface APIKeySecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class APIKeySecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: APIKeySecuritySchemeElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'APIKeySecurityScheme']>; + + constructor(options: APIKeySecuritySchemeVisitorOptions) { + super(options); + this.element = new APIKeySecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'APIKeySecurityScheme']); + } +} + +export default APIKeySecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/authorization-code-oauth-flow/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/authorization-code-oauth-flow/index.ts new file mode 100644 index 0000000000..0787d13d37 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/authorization-code-oauth-flow/index.ts @@ -0,0 +1,35 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import AuthorizationCodeOAuthFlowElement from '../../../../elements/AuthorizationCodeOAuthFlow.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface AuthorizationCodeOAuthFlowVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class AuthorizationCodeOAuthFlowVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: AuthorizationCodeOAuthFlowElement; + + declare protected readonly specPath: SpecPath< + ['document', 'objects', 'AuthorizationCodeOAuthFlow'] + >; + + constructor(options: AuthorizationCodeOAuthFlowVisitorOptions) { + super(options); + this.element = new AuthorizationCodeOAuthFlowElement(); + this.specPath = always(['document', 'objects', 'AuthorizationCodeOAuthFlow']); + } +} + +export default AuthorizationCodeOAuthFlowVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/client-credentials-oauth-flow/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/client-credentials-oauth-flow/index.ts new file mode 100644 index 0000000000..9a784fb3d5 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/client-credentials-oauth-flow/index.ts @@ -0,0 +1,35 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import ClientCredentialsOAuthFlowElement from '../../../../elements/ClientCredentialsOAuthFlow.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface ClientCredentialsOAuthFlowVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class ClientCredentialsOAuthFlowVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: ClientCredentialsOAuthFlowElement; + + declare protected readonly specPath: SpecPath< + ['document', 'objects', 'ClientCredentialsOAuthFlow'] + >; + + constructor(options: ClientCredentialsOAuthFlowVisitorOptions) { + super(options); + this.element = new ClientCredentialsOAuthFlowElement(); + this.specPath = always(['document', 'objects', 'ClientCredentialsOAuthFlow']); + } +} + +export default ClientCredentialsOAuthFlowVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/device-code-oauth-flow/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/device-code-oauth-flow/index.ts new file mode 100644 index 0000000000..7ec4ee78f2 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/device-code-oauth-flow/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import DeviceCodeOAuthFlowElement from '../../../../elements/DeviceCodeOAuthFlow.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface DeviceCodeOAuthFlowVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class DeviceCodeOAuthFlowVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: DeviceCodeOAuthFlowElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'DeviceCodeOAuthFlow']>; + + constructor(options: DeviceCodeOAuthFlowVisitorOptions) { + super(options); + this.element = new DeviceCodeOAuthFlowElement(); + this.specPath = always(['document', 'objects', 'DeviceCodeOAuthFlow']); + } +} + +export default DeviceCodeOAuthFlowVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/http-auth-security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/http-auth-security-scheme/index.ts new file mode 100644 index 0000000000..8dedc45989 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/http-auth-security-scheme/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import HTTPAuthSecuritySchemeElement from '../../../../elements/HTTPAuthSecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface HTTPAuthSecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class HTTPAuthSecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: HTTPAuthSecuritySchemeElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'HTTPAuthSecurityScheme']>; + + constructor(options: HTTPAuthSecuritySchemeVisitorOptions) { + super(options); + this.element = new HTTPAuthSecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'HTTPAuthSecurityScheme']); + } +} + +export default HTTPAuthSecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/mutual-tls-security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/mutual-tls-security-scheme/index.ts new file mode 100644 index 0000000000..50fd8838a4 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/mutual-tls-security-scheme/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import MutualTlsSecuritySchemeElement from '../../../../elements/MutualTlsSecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface MutualTlsSecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class MutualTlsSecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: MutualTlsSecuritySchemeElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'MutualTlsSecurityScheme']>; + + constructor(options: MutualTlsSecuritySchemeVisitorOptions) { + super(options); + this.element = new MutualTlsSecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'MutualTlsSecurityScheme']); + } +} + +export default MutualTlsSecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth-flows/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth-flows/index.ts new file mode 100644 index 0000000000..ee231587fa --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth-flows/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import OAuthFlowsElement from '../../../../elements/OAuthFlows.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface OAuthFlowsVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class OAuthFlowsVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: OAuthFlowsElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'OAuthFlows']>; + + constructor(options: OAuthFlowsVisitorOptions) { + super(options); + this.element = new OAuthFlowsElement(); + this.specPath = always(['document', 'objects', 'OAuthFlows']); + } +} + +export default OAuthFlowsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth2-security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth2-security-scheme/index.ts new file mode 100644 index 0000000000..21861c083b --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/oauth2-security-scheme/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import OAuth2SecuritySchemeElement from '../../../../elements/OAuth2SecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface OAuth2SecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class OAuth2SecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: OAuth2SecuritySchemeElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'OAuth2SecurityScheme']>; + + constructor(options: OAuth2SecuritySchemeVisitorOptions) { + super(options); + this.element = new OAuth2SecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'OAuth2SecurityScheme']); + } +} + +export default OAuth2SecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/open-id-connect-security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/open-id-connect-security-scheme/index.ts new file mode 100644 index 0000000000..b5f381aca9 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/open-id-connect-security-scheme/index.ts @@ -0,0 +1,35 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import OpenIdConnectSecuritySchemeElement from '../../../../elements/OpenIdConnectSecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface OpenIdConnectSecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class OpenIdConnectSecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: OpenIdConnectSecuritySchemeElement; + + declare protected readonly specPath: SpecPath< + ['document', 'objects', 'OpenIdConnectSecurityScheme'] + >; + + constructor(options: OpenIdConnectSecuritySchemeVisitorOptions) { + super(options); + this.element = new OpenIdConnectSecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'OpenIdConnectSecurityScheme']); + } +} + +export default OpenIdConnectSecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/SchemesVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/SchemesVisitor.ts new file mode 100644 index 0000000000..46118a1f6d --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/SchemesVisitor.ts @@ -0,0 +1,31 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; +import { ObjectElement } from '@swagger-api/apidom-core'; + +import MapVisitor, { MapVisitorOptions, SpecPath } from '../../generics/MapVisitor.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; + +/** + * @public + */ +export interface SecurityRequirementSchemesVisitorOptions + extends MapVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SecurityRequirementSchemesVisitor extends Mixin(MapVisitor, FallbackVisitor) { + public readonly element: ObjectElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'StringList']>; + + constructor(options: SecurityRequirementSchemesVisitorOptions) { + super(options); + this.element = new ObjectElement(); + this.element.classes.push('security-requirement-schemes'); + this.specPath = always(['document', 'objects', 'StringList']); + } +} + +export default SecurityRequirementSchemesVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/index.ts new file mode 100644 index 0000000000..43f5c7b7d3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-requirement/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import SecurityRequirementElement from '../../../../elements/SecurityRequirement.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface SecurityRequirementVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SecurityRequirementVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: SecurityRequirementElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'SecurityRequirement']>; + + constructor(options: SecurityRequirementVisitorOptions) { + super(options); + this.element = new SecurityRequirementElement(); + this.specPath = always(['document', 'objects', 'SecurityRequirement']); + } +} + +export default SecurityRequirementVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-scheme/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-scheme/index.ts new file mode 100644 index 0000000000..b7b6057c55 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/security-scheme/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import SecuritySchemeElement from '../../../../elements/SecurityScheme.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface SecuritySchemeVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class SecuritySchemeVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: SecuritySchemeElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'SecurityScheme']>; + + constructor(options: SecuritySchemeVisitorOptions) { + super(options); + this.element = new SecuritySchemeElement(); + this.specPath = always(['document', 'objects', 'SecurityScheme']); + } +} + +export default SecuritySchemeVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/string-list/index.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/string-list/index.ts new file mode 100644 index 0000000000..70ce846007 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/a2a-1/string-list/index.ts @@ -0,0 +1,33 @@ +import { Mixin } from 'ts-mixer'; +import { always } from 'ramda'; + +import StringListElement from '../../../../elements/StringList.ts'; +import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts'; +import FixedFieldsVisitor, { + FixedFieldsVisitorOptions, + SpecPath, +} from '../../generics/FixedFieldsVisitor.ts'; + +/** + * @public + */ +export interface StringListVisitorOptions + extends FixedFieldsVisitorOptions, + FallbackVisitorOptions {} + +/** + * @public + */ +class StringListVisitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) { + declare public readonly element: StringListElement; + + declare protected readonly specPath: SpecPath<['document', 'objects', 'StringList']>; + + constructor(options: StringListVisitorOptions) { + super(options); + this.element = new StringListElement(); + this.specPath = always(['document', 'objects', 'StringList']); + } +} + +export default StringListVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/FixedFieldsVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/FixedFieldsVisitor.ts new file mode 100644 index 0000000000..eb6785b4cc --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/FixedFieldsVisitor.ts @@ -0,0 +1,96 @@ +import { + isStringElement, + MemberElement, + Element, + BREAK, + cloneDeep, + toValue, + ObjectElement, +} from '@swagger-api/apidom-core'; + +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import { isA2ASpecificationExtension } from '../../predicates.ts'; + +/** + * @public + */ +export type SpecPath = (element: unknown) => T; + +/** + * @public + */ +export interface FixedFieldsVisitorOptions extends SpecificationVisitorOptions { + readonly specPath: SpecPath; + readonly ignoredFields?: string[]; + readonly canSupportSpecificationExtensions?: boolean; + readonly specificationExtensionPredicate?: typeof isA2ASpecificationExtension; +} + +/** + * @public + */ +class FixedFieldsVisitor extends SpecificationVisitor { + protected specPath: SpecPath; + + protected ignoredFields: string[] = []; + + protected canSupportSpecificationExtensions: boolean = false; + + protected specificationExtensionPredicate = isA2ASpecificationExtension; + + constructor({ + specPath, + ignoredFields, + canSupportSpecificationExtensions, + specificationExtensionPredicate, + ...rest + }: FixedFieldsVisitorOptions) { + super({ ...rest }); + this.specPath = specPath; + this.ignoredFields = ignoredFields || []; + + if (typeof canSupportSpecificationExtensions === 'boolean') { + this.canSupportSpecificationExtensions = canSupportSpecificationExtensions; + } + if (typeof specificationExtensionPredicate === 'function') { + this.specificationExtensionPredicate = specificationExtensionPredicate; + } + } + + ObjectElement(objectElement: ObjectElement) { + const specPath = this.specPath(objectElement); + const fields = this.retrieveFixedFields(specPath); + + // @ts-ignore + objectElement.forEach((value: Element, key: Element, memberElement: MemberElement) => { + if ( + isStringElement(key) && + fields.includes(toValue(key)) && + !this.ignoredFields.includes(toValue(key)) + ) { + const fixedFieldElement = this.toRefractedElement( + [...specPath, 'fixedFields', toValue(key)], + value, + ); + const newMemberElement = new MemberElement(cloneDeep(key), fixedFieldElement); + this.copyMetaAndAttributes(memberElement, newMemberElement); + newMemberElement.classes.push('fixed-field'); + this.element.content.push(newMemberElement); + } else if ( + this.canSupportSpecificationExtensions && + this.specificationExtensionPredicate(memberElement) + ) { + const extensionElement = this.toRefractedElement(['document', 'extension'], memberElement); + this.element.content.push(extensionElement); + } else if (!this.ignoredFields.includes(toValue(key))) { + this.element.content.push(cloneDeep(memberElement)); + } + }); + + this.copyMetaAndAttributes(objectElement, this.element); + + return BREAK; + } +} + +export default FixedFieldsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/MapVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/MapVisitor.ts new file mode 100644 index 0000000000..ef8f8f7398 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/MapVisitor.ts @@ -0,0 +1,25 @@ +import { isNonEmptyString } from 'ramda-adjunct'; + +import PatternedFieldsVisitor, { + SpecPath, + PatternedFieldsVisitorOptions, +} from './PatternedFieldsVisitor.ts'; + +export type { SpecPath }; + +/** + * @public + */ +export interface MapVisitorOptions extends PatternedFieldsVisitorOptions {} + +/** + * @public + */ +class MapVisitor extends PatternedFieldsVisitor { + constructor(options: MapVisitorOptions) { + super(options); + this.fieldPatternPredicate = isNonEmptyString; + } +} + +export default MapVisitor; diff --git a/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/PatternedFieldsVisitor.ts b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/PatternedFieldsVisitor.ts new file mode 100644 index 0000000000..27d84c9e40 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/refractor/visitors/generics/PatternedFieldsVisitor.ts @@ -0,0 +1,95 @@ +import { F as stubFalse } from 'ramda'; +import { + ObjectElement, + Element, + MemberElement, + BREAK, + cloneDeep, + toValue, +} from '@swagger-api/apidom-core'; + +import type { SpecPath } from './FixedFieldsVisitor.ts'; +import SpecificationVisitor, { SpecificationVisitorOptions } from '../SpecificationVisitor.ts'; +import { isA2ASpecificationExtension } from '../../predicates.ts'; + +export type { SpecPath }; + +/** + * @public + */ +export interface PatternedFieldsVisitorOptions extends SpecificationVisitorOptions { + readonly specPath: SpecPath; + readonly ignoredFields?: string[]; + readonly fieldPatternPredicate?: (...args: unknown[]) => boolean; + readonly canSupportSpecificationExtensions?: boolean; + readonly specificationExtensionPredicate?: typeof isA2ASpecificationExtension; +} + +/** + * @public + */ +class PatternedFieldsVisitor extends SpecificationVisitor { + protected specPath: SpecPath; + + protected ignoredFields: string[]; + + protected fieldPatternPredicate: (value: unknown) => boolean = stubFalse; + + protected canSupportSpecificationExtensions: boolean = false; + + protected specificationExtensionPredicate = isA2ASpecificationExtension; + + constructor({ + specPath, + ignoredFields, + fieldPatternPredicate, + canSupportSpecificationExtensions, + specificationExtensionPredicate, + ...rest + }: PatternedFieldsVisitorOptions) { + super({ ...rest }); + this.specPath = specPath; + this.ignoredFields = ignoredFields || []; + + if (typeof fieldPatternPredicate === 'function') { + this.fieldPatternPredicate = fieldPatternPredicate; + } + if (typeof canSupportSpecificationExtensions === 'boolean') { + this.canSupportSpecificationExtensions = canSupportSpecificationExtensions; + } + if (typeof specificationExtensionPredicate === 'function') { + this.specificationExtensionPredicate = specificationExtensionPredicate; + } + } + + ObjectElement(objectElement: ObjectElement) { + // @ts-ignore + objectElement.forEach((value: Element, key: Element, memberElement: MemberElement) => { + if ( + this.canSupportSpecificationExtensions && + this.specificationExtensionPredicate(memberElement) + ) { + const extensionElement = this.toRefractedElement(['document', 'extension'], memberElement); + this.element.content.push(extensionElement); + } else if ( + !this.ignoredFields.includes(toValue(key)) && + this.fieldPatternPredicate(toValue(key)) + ) { + const specPath = this.specPath(value); + const patternedFieldElement = this.toRefractedElement(specPath, value); + const newMemberElement = new MemberElement(cloneDeep(key), patternedFieldElement); + this.copyMetaAndAttributes(memberElement, newMemberElement); + newMemberElement.classes.push('patterned-field'); + this.element.content.push(newMemberElement); + } else if (!this.ignoredFields.includes(toValue(key))) { + this.element.content.push(cloneDeep(memberElement)); + } + }); + + this.copyMetaAndAttributes(objectElement, this.element); + + return BREAK; + } +} + +export default PatternedFieldsVisitor; diff --git a/packages/apidom-ns-a2a-1/src/traversal/visitor.ts b/packages/apidom-ns-a2a-1/src/traversal/visitor.ts new file mode 100644 index 0000000000..3151046a37 --- /dev/null +++ b/packages/apidom-ns-a2a-1/src/traversal/visitor.ts @@ -0,0 +1,42 @@ +import { keyMap as keyMapBase, isElement, Element } from '@swagger-api/apidom-core'; + +/** + * @public + */ +export const getNodeType = (element: T): string | undefined => { + if (!isElement(element)) { + return undefined; + } + return `${element.element.charAt(0).toUpperCase() + element.element.slice(1)}Element`; +}; + +/** + * A2A Protocol v1.0 + * @public + */ +export const keyMap = { + A2aAgentCard1Element: ['content'], + AgentCapabilitiesElement: ['content'], + AgentExtensionElement: ['content'], + AgentProviderElement: ['content'], + AgentInterfaceElement: ['content'], + AgentSkillElement: ['content'], + AgentCardSignatureElement: ['content'], + SecurityRequirementElement: ['content'], + SecuritySchemeElement: ['content'], + // Note: keyMap keys must match the output of getNodeType (above), which + // upper-cases only the first character of `element.element`. For names that + // begin with an acronym (API, HTTP, OAuth) this differs from the natural + // PascalCase class name — keep the keyMap key matching getNodeType. + ApiKeySecuritySchemeElement: ['content'], + HttpAuthSecuritySchemeElement: ['content'], + MutualTlsSecuritySchemeElement: ['content'], + Oauth2SecuritySchemeElement: ['content'], + OpenIdConnectSecuritySchemeElement: ['content'], + OauthFlowsElement: ['content'], + AuthorizationCodeOAuthFlowElement: ['content'], + ClientCredentialsOAuthFlowElement: ['content'], + DeviceCodeOAuthFlowElement: ['content'], + StringListElement: ['content'], + ...keyMapBase, +}; diff --git a/packages/apidom-ns-a2a-1/test/.eslintrc b/packages/apidom-ns-a2a-1/test/.eslintrc new file mode 100644 index 0000000000..5cc99718c6 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/.eslintrc @@ -0,0 +1,57 @@ +{ + "env": { + "mocha": true + }, + "globals": { + "document": true + }, + "plugins": [ + "mocha" + ], + "rules": { + "no-void": 0, + "no-underscore-dangle": 0, + "func-names": 0, + "prefer-arrow-callback": 0, + "no-array-constructor": 0, + "prefer-rest-params": 0, + "no-new-wrappers": 0, + "mocha/no-skipped-tests": 2, + "mocha/handle-done-callback": 2, + "mocha/valid-suite-description": 2, + "mocha/no-mocha-arrows": 2, + "mocha/no-hooks-for-single-case": 2, + "mocha/no-sibling-hooks": 2, + "mocha/no-top-level-hooks": 2, + "mocha/no-identical-title": 2, + "mocha/no-nested-tests": 2, + "mocha/no-exclusive-tests": 2, + "max-classes-per-file": 0, + "@typescript-eslint/no-unused-expressions": 0, + "import/no-relative-packages": 0, + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "UPPER_CASE"], + "leadingUnderscore": "forbid" + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__dirname$", + "match": true + } + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__filename$", + "match": true + } + } + ] + } +} diff --git a/packages/apidom-ns-a2a-1/test/fixtures/real-world-planner-agent.json b/packages/apidom-ns-a2a-1/test/fixtures/real-world-planner-agent.json new file mode 100644 index 0000000000..4df8eadde3 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/fixtures/real-world-planner-agent.json @@ -0,0 +1,32 @@ +{ + "name": "Langraph Planner Agent", + "description": "Helps breakdown a request in to actionable tasks", + "url": "http://localhost:10102/", + "version": "1.0.0", + "capabilities": { + "streaming": true, + "pushNotifications": true, + "stateTransitionHistory": false + }, + "defaultInputModes": [ + "text", + "text/plain" + ], + "defaultOutputModes": [ + "text", + "text/plain" + ], + "skills": [ + { + "id": "planner", + "name": "Task Planner", + "description": "Helps breakdown a request in to actionable tasks", + "tags": [ + "planner" + ], + "examples": [ + "Plan my business trip from San Francisco to London, submit an expense report" + ] + } + ] +} diff --git a/packages/apidom-ns-a2a-1/test/mocha-bootstrap.ts b/packages/apidom-ns-a2a-1/test/mocha-bootstrap.ts new file mode 100644 index 0000000000..aec560d03f --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/mocha-bootstrap.ts @@ -0,0 +1,11 @@ +import * as chai from 'chai'; +import { jestSnapshotPlugin, addSerializer } from 'mocha-chai-jest-snapshot'; + +// @ts-ignore +import * as jestApiDOMSerializer from '../../../scripts/jest-serializer-apidom.mjs'; +// @ts-ignore +import * as jestStringSerializer from '../../../scripts/jest-serializer-string.mjs'; + +chai.use(jestSnapshotPlugin()); +addSerializer(jestApiDOMSerializer); +addSerializer(jestStringSerializer); diff --git a/packages/apidom-ns-a2a-1/test/predicates.ts b/packages/apidom-ns-a2a-1/test/predicates.ts new file mode 100644 index 0000000000..ac70e2a59c --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/predicates.ts @@ -0,0 +1,152 @@ +import { assert } from 'chai'; + +import { + isAgentCardElement, + isAgentCapabilitiesElement, + isAgentExtensionElement, + isAgentProviderElement, + isAgentInterfaceElement, + isAgentSkillElement, + isAgentCardSignatureElement, + isSecurityRequirementElement, + isSecuritySchemeElement, + isAPIKeySecuritySchemeElement, + isHTTPAuthSecuritySchemeElement, + isMutualTlsSecuritySchemeElement, + isOAuth2SecuritySchemeElement, + isOpenIdConnectSecuritySchemeElement, + isOAuthFlowsElement, + isAuthorizationCodeOAuthFlowElement, + isClientCredentialsOAuthFlowElement, + isDeviceCodeOAuthFlowElement, + isStringListElement, + AgentCardElement, + AgentCapabilitiesElement, + AgentExtensionElement, + AgentProviderElement, + AgentInterfaceElement, + AgentSkillElement, + AgentCardSignatureElement, + SecurityRequirementElement, + SecuritySchemeElement, + APIKeySecuritySchemeElement, + HTTPAuthSecuritySchemeElement, + MutualTlsSecuritySchemeElement, + OAuth2SecuritySchemeElement, + OpenIdConnectSecuritySchemeElement, + OAuthFlowsElement, + AuthorizationCodeOAuthFlowElement, + ClientCredentialsOAuthFlowElement, + DeviceCodeOAuthFlowElement, + StringListElement, +} from '../src/index.ts'; + +interface PredicateCase { + name: string; + predicate: (e: unknown) => boolean; + Cls: new () => unknown; +} + +const cases: PredicateCase[] = [ + { name: 'isAgentCardElement', predicate: isAgentCardElement, Cls: AgentCardElement }, + { + name: 'isAgentCapabilitiesElement', + predicate: isAgentCapabilitiesElement, + Cls: AgentCapabilitiesElement, + }, + { + name: 'isAgentExtensionElement', + predicate: isAgentExtensionElement, + Cls: AgentExtensionElement, + }, + { name: 'isAgentProviderElement', predicate: isAgentProviderElement, Cls: AgentProviderElement }, + { + name: 'isAgentInterfaceElement', + predicate: isAgentInterfaceElement, + Cls: AgentInterfaceElement, + }, + { name: 'isAgentSkillElement', predicate: isAgentSkillElement, Cls: AgentSkillElement }, + { + name: 'isAgentCardSignatureElement', + predicate: isAgentCardSignatureElement, + Cls: AgentCardSignatureElement, + }, + { + name: 'isSecurityRequirementElement', + predicate: isSecurityRequirementElement, + Cls: SecurityRequirementElement, + }, + { + name: 'isSecuritySchemeElement', + predicate: isSecuritySchemeElement, + Cls: SecuritySchemeElement, + }, + { + name: 'isAPIKeySecuritySchemeElement', + predicate: isAPIKeySecuritySchemeElement, + Cls: APIKeySecuritySchemeElement, + }, + { + name: 'isHTTPAuthSecuritySchemeElement', + predicate: isHTTPAuthSecuritySchemeElement, + Cls: HTTPAuthSecuritySchemeElement, + }, + { + name: 'isMutualTlsSecuritySchemeElement', + predicate: isMutualTlsSecuritySchemeElement, + Cls: MutualTlsSecuritySchemeElement, + }, + { + name: 'isOAuth2SecuritySchemeElement', + predicate: isOAuth2SecuritySchemeElement, + Cls: OAuth2SecuritySchemeElement, + }, + { + name: 'isOpenIdConnectSecuritySchemeElement', + predicate: isOpenIdConnectSecuritySchemeElement, + Cls: OpenIdConnectSecuritySchemeElement, + }, + { name: 'isOAuthFlowsElement', predicate: isOAuthFlowsElement, Cls: OAuthFlowsElement }, + { + name: 'isAuthorizationCodeOAuthFlowElement', + predicate: isAuthorizationCodeOAuthFlowElement, + Cls: AuthorizationCodeOAuthFlowElement, + }, + { + name: 'isClientCredentialsOAuthFlowElement', + predicate: isClientCredentialsOAuthFlowElement, + Cls: ClientCredentialsOAuthFlowElement, + }, + { + name: 'isDeviceCodeOAuthFlowElement', + predicate: isDeviceCodeOAuthFlowElement, + Cls: DeviceCodeOAuthFlowElement, + }, + { name: 'isStringListElement', predicate: isStringListElement, Cls: StringListElement }, +]; + +describe('predicates', function () { + cases.forEach(({ name, predicate, Cls }) => { + context(name, function () { + context(`given matching ${Cls.name} instance`, function () { + specify('should return true', function () { + assert.isTrue(predicate(new Cls())); + }); + }); + + context('given non-matching values', function () { + specify('should return false for plain object', function () { + assert.isFalse(predicate({})); + }); + + specify('should return false for null', function () { + assert.isFalse(predicate(null)); + }); + + specify('should return false for undefined', function () { + assert.isFalse(predicate(undefined)); + }); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/canonicalize.ts b/packages/apidom-ns-a2a-1/test/refractor/canonicalize.ts new file mode 100644 index 0000000000..88f7b8ff56 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/canonicalize.ts @@ -0,0 +1,102 @@ +import { assert, expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentCardElement, isAgentCardElement } from '../../src/index.ts'; + +describe('refractor', function () { + context('snake_case canonicalisation', function () { + specify('should refract snake_case keys identically to camelCase', function () { + const camelCase = AgentCardElement.refract({ + name: 'Demo', + url: 'https://demo.example', + version: '1.0.0', + iconUrl: 'https://demo.example/icon.png', + documentationUrl: 'https://demo.example/docs', + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + capabilities: { + streaming: true, + pushNotifications: false, + extendedAgentCard: false, + }, + supportedInterfaces: [ + { url: 'https://demo.example/grpc', protocolBinding: 'GRPC', protocolVersion: '1.0' }, + ], + skills: [ + { + id: 'skill-1', + name: 'Skill 1', + description: 'A skill', + inputModes: ['text/plain'], + outputModes: ['application/json'], + }, + ], + }); + + const snakeCase = AgentCardElement.refract({ + name: 'Demo', + url: 'https://demo.example', + version: '1.0.0', + icon_url: 'https://demo.example/icon.png', + documentation_url: 'https://demo.example/docs', + default_input_modes: ['text/plain'], + default_output_modes: ['application/json'], + capabilities: { + streaming: true, + push_notifications: false, + extended_agent_card: false, + }, + supported_interfaces: [ + { url: 'https://demo.example/grpc', protocol_binding: 'GRPC', protocol_version: '1.0' }, + ], + skills: [ + { + id: 'skill-1', + name: 'Skill 1', + description: 'A skill', + input_modes: ['text/plain'], + output_modes: ['application/json'], + }, + ], + }); + + assert.isTrue(isAgentCardElement(camelCase)); + assert.isTrue(isAgentCardElement(snakeCase)); + // sexprs is structural; both should produce the same semantic tree + expect(sexprs(snakeCase)).to.equal(sexprs(camelCase)); + }); + + specify('should canonicalise security scheme oneof subfields', function () { + const snake = AgentCardElement.refract({ + name: 'a', + url: 'https://x', + version: '1.0.0', + capabilities: {}, + defaultInputModes: [], + defaultOutputModes: [], + skills: [], + security_schemes: { + apiKey: { + api_key_security_scheme: { name: 'X-API-Key', location: 'header' }, + }, + }, + }); + const camel = AgentCardElement.refract({ + name: 'a', + url: 'https://x', + version: '1.0.0', + capabilities: {}, + defaultInputModes: [], + defaultOutputModes: [], + skills: [], + securitySchemes: { + apiKey: { + apiKeySecurityScheme: { name: 'X-API-Key', location: 'header' }, + }, + }, + }); + + expect(sexprs(snake)).to.equal(sexprs(camel)); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..9457d09a68 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements APIKeySecuritySchemeElement should refract to semantic ApiDOM tree 1`] = ` +(ApiKeySecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/index.ts new file mode 100644 index 0000000000..5ffedc517c --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/APIKeySecurityScheme/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { APIKeySecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('APIKeySecuritySchemeElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = APIKeySecuritySchemeElement.refract({ + description: 'API key', + name: 'X-API-Key', + location: 'header', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..7a7d3638e8 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/__snapshots__/index.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentCapabilitiesElement should refract to semantic ApiDOM tree 1`] = ` +(AgentCapabilitiesElement + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (ArrayElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/index.ts new file mode 100644 index 0000000000..0e5520ac69 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCapabilities/index.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentCapabilitiesElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentCapabilitiesElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentCapabilitiesElement.refract({ + streaming: true, + pushNotifications: false, + extendedAgentCard: false, + extensions: [], + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..dc05d72dc5 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/__snapshots__/index.ts.snap @@ -0,0 +1,142 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentCardElement should refract a minimal agent card 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentProviderElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))) + (MemberElement + (StringElement) + (AgentCapabilitiesElement + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (AgentInterfaceElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))))) + (MemberElement + (StringElement) + (ArrayElement + (AgentSkillElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))))))) +`; + +exports[`refractor elements AgentCardElement should refract an agent card with security schemes and a signature 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentCapabilitiesElement)) + (MemberElement + (StringElement) + (ArrayElement)) + (MemberElement + (StringElement) + (ArrayElement)) + (MemberElement + (StringElement) + (ArrayElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (SecuritySchemeElement + (MemberElement + (StringElement) + (ApiKeySecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))))))) + (MemberElement + (StringElement) + (ArrayElement + (SecurityRequirementElement + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringListElement + (MemberElement + (StringElement) + (ArrayElement))))))))) + (MemberElement + (StringElement) + (ArrayElement + (AgentCardSignatureElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringElement)))))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/index.ts new file mode 100644 index 0000000000..93a84078c6 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCard/index.ts @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentCardElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentCardElement', function () { + specify('should refract a minimal agent card', function () { + const element = AgentCardElement.refract({ + name: 'Demo Agent', + description: 'A demo A2A agent', + version: '1.0.0', + iconUrl: 'https://demo.example/icon.png', + documentationUrl: 'https://demo.example/docs', + provider: { + organization: 'Demo Inc', + url: 'https://demo.example', + }, + capabilities: { + streaming: true, + pushNotifications: false, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + supportedInterfaces: [ + { + url: 'https://demo.example/a2a', + protocolBinding: 'JSONRPC', + protocolVersion: '1.0', + }, + ], + skills: [ + { + id: 'lookup', + name: 'Lookup', + description: 'Looks up data', + tags: ['search'], + }, + ], + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + + specify('should refract an agent card with security schemes and a signature', function () { + const element = AgentCardElement.refract({ + name: 'Secure Agent', + version: '1.0.0', + capabilities: {}, + defaultInputModes: [], + defaultOutputModes: [], + skills: [], + securitySchemes: { + apiKeyAuth: { + apiKeySecurityScheme: { + name: 'X-API-Key', + location: 'header', + }, + }, + }, + securityRequirements: [{ schemes: { apiKeyAuth: { list: [] } } }], + signatures: [ + { + protected: 'eyJhbGciOiJFUzI1NiJ9', + signature: 'base64url-sig', + header: { kid: 'key-1' }, + }, + ], + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..7dd46761ee --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/__snapshots__/index.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentCardSignatureElement should refract to semantic ApiDOM tree 1`] = ` +(AgentCardSignatureElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/index.ts new file mode 100644 index 0000000000..f206ad18bf --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentCardSignature/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentCardSignatureElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentCardSignatureElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentCardSignatureElement.refract({ + protected: 'eyJ...', + signature: 'sig...', + header: { alg: 'ES256' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..8e705dd743 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/__snapshots__/index.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentExtensionElement should refract to semantic ApiDOM tree 1`] = ` +(AgentExtensionElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/index.ts new file mode 100644 index 0000000000..3b4131773f --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentExtension/index.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentExtensionElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentExtensionElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentExtensionElement.refract({ + uri: 'urn:example:ext', + description: 'demo', + required: true, + params: { foo: 'bar' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..03a6cf7e27 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/__snapshots__/index.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentInterfaceElement should refract to semantic ApiDOM tree 1`] = ` +(AgentInterfaceElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/index.ts new file mode 100644 index 0000000000..c34a0d991a --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentInterface/index.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentInterfaceElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentInterfaceElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentInterfaceElement.refract({ + url: 'https://example.com/a2a', + protocolBinding: 'JSONRPC', + protocolVersion: '1.0', + tenant: 'tenant-a', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..0eee7e4ff2 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/__snapshots__/index.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentProviderElement should refract to semantic ApiDOM tree 1`] = ` +(AgentProviderElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/index.ts new file mode 100644 index 0000000000..9b95f4bcb4 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentProvider/index.ts @@ -0,0 +1,19 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentProviderElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentProviderElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentProviderElement.refract({ + organization: 'Acme', + url: 'https://acme.example', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..a747b0ee0f --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/__snapshots__/index.ts.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AgentSkillElement should refract to semantic ApiDOM tree 1`] = ` +(AgentSkillElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (SecurityRequirementElement + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (ArrayElement + (StringElement))))))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/index.ts new file mode 100644 index 0000000000..71a3585acc --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AgentSkill/index.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AgentSkillElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AgentSkillElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AgentSkillElement.refract({ + id: 'sk-1', + name: 'Lookup', + description: 'Looks up data', + tags: ['data'], + examples: ['Find X'], + inputModes: ['text/plain'], + outputModes: ['application/json'], + securityRequirements: [{ schemes: { oauth: ['read'] } }], + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..22b2322f73 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/__snapshots__/index.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements AuthorizationCodeOAuthFlowElement should refract to semantic ApiDOM tree 1`] = ` +(AuthorizationCodeOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/index.ts new file mode 100644 index 0000000000..d73d8a3f96 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/AuthorizationCodeOAuthFlow/index.ts @@ -0,0 +1,22 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { AuthorizationCodeOAuthFlowElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('AuthorizationCodeOAuthFlowElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = AuthorizationCodeOAuthFlowElement.refract({ + authorizationUrl: 'https://idp.example/authorize', + tokenUrl: 'https://idp.example/token', + refreshUrl: 'https://idp.example/refresh', + pkceRequired: true, + scopes: { read: 'Read access' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..10b4408895 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/__snapshots__/index.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements ClientCredentialsOAuthFlowElement should refract to semantic ApiDOM tree 1`] = ` +(ClientCredentialsOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/index.ts new file mode 100644 index 0000000000..090efbca2e --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/ClientCredentialsOAuthFlow/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { ClientCredentialsOAuthFlowElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('ClientCredentialsOAuthFlowElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = ClientCredentialsOAuthFlowElement.refract({ + tokenUrl: 'https://idp.example/token', + refreshUrl: 'https://idp.example/refresh', + scopes: { admin: 'Admin' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..764ba2fc02 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/__snapshots__/index.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements DeviceCodeOAuthFlowElement should refract to semantic ApiDOM tree 1`] = ` +(DeviceCodeOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/index.ts new file mode 100644 index 0000000000..b40c2dc3ee --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/DeviceCodeOAuthFlow/index.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { DeviceCodeOAuthFlowElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('DeviceCodeOAuthFlowElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = DeviceCodeOAuthFlowElement.refract({ + deviceAuthorizationUrl: 'https://idp.example/device', + tokenUrl: 'https://idp.example/token', + refreshUrl: 'https://idp.example/refresh', + scopes: {}, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..07a39181e0 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements HTTPAuthSecuritySchemeElement should refract to semantic ApiDOM tree 1`] = ` +(HttpAuthSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/index.ts new file mode 100644 index 0000000000..d625863837 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/HTTPAuthSecurityScheme/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { HTTPAuthSecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('HTTPAuthSecuritySchemeElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = HTTPAuthSecuritySchemeElement.refract({ + description: 'Bearer', + scheme: 'bearer', + bearerFormat: 'JWT', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..dcf01e9f06 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements MutualTlsSecuritySchemeElement should refract to semantic ApiDOM tree 1`] = ` +(MutualTlsSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/index.ts new file mode 100644 index 0000000000..88239b1916 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/MutualTlsSecurityScheme/index.ts @@ -0,0 +1,18 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { MutualTlsSecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('MutualTlsSecuritySchemeElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = MutualTlsSecuritySchemeElement.refract({ + description: 'Client cert required', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..f46414c262 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements OAuth2SecuritySchemeElement should refract to semantic ApiDOM tree 1`] = ` +(Oauth2SecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (OauthFlowsElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/index.ts new file mode 100644 index 0000000000..95e224f0b8 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuth2SecurityScheme/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { OAuth2SecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('OAuth2SecuritySchemeElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = OAuth2SecuritySchemeElement.refract({ + description: 'OAuth2', + flows: {}, + oauth2MetadataUrl: 'https://idp.example/.well-known/oauth-authorization-server', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..463897facc --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/__snapshots__/index.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements OAuthFlowsElement should refract to semantic ApiDOM tree 1`] = ` +(OauthFlowsElement + (MemberElement + (StringElement) + (AuthorizationCodeOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))) + (MemberElement + (StringElement) + (ClientCredentialsOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)))) + (MemberElement + (StringElement) + (DeviceCodeOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/index.ts new file mode 100644 index 0000000000..e480a6ad09 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OAuthFlows/index.ts @@ -0,0 +1,28 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { OAuthFlowsElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('OAuthFlowsElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = OAuthFlowsElement.refract({ + authorizationCode: { + authorizationUrl: 'https://x.example/a', + tokenUrl: 'https://x.example/t', + }, + clientCredentials: { + tokenUrl: 'https://x.example/t', + }, + deviceCode: { + deviceAuthorizationUrl: 'https://x.example/d', + tokenUrl: 'https://x.example/t', + }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..ac0e40fae7 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements OpenIdConnectSecuritySchemeElement should refract to semantic ApiDOM tree 1`] = ` +(OpenIdConnectSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/index.ts new file mode 100644 index 0000000000..0c043f6938 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/OpenIdConnectSecurityScheme/index.ts @@ -0,0 +1,19 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { OpenIdConnectSecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('OpenIdConnectSecuritySchemeElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = OpenIdConnectSecuritySchemeElement.refract({ + description: 'OIDC', + openIdConnectUrl: 'https://idp.example/.well-known/openid-configuration', + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..67a298db3e --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/__snapshots__/index.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements SecurityRequirementElement should refract to semantic ApiDOM tree 1`] = ` +(SecurityRequirementElement + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringListElement + (MemberElement + (StringElement) + (ArrayElement + (StringElement)))))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/index.ts new file mode 100644 index 0000000000..355b63f87f --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityRequirement/index.ts @@ -0,0 +1,18 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { SecurityRequirementElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('SecurityRequirementElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = SecurityRequirementElement.refract({ + schemes: { 'oauth-scheme': { list: ['read'] } }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..47028072b4 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/__snapshots__/index.ts.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements SecuritySchemeElement should refract apiKeySecurityScheme variant 1`] = ` +(SecuritySchemeElement + (MemberElement + (StringElement) + (ApiKeySecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))))) +`; + +exports[`refractor elements SecuritySchemeElement should refract httpAuthSecurityScheme variant 1`] = ` +(SecuritySchemeElement + (MemberElement + (StringElement) + (HttpAuthSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement))))) +`; + +exports[`refractor elements SecuritySchemeElement should refract mtlsSecurityScheme variant 1`] = ` +(SecuritySchemeElement + (MemberElement + (StringElement) + (MutualTlsSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement))))) +`; + +exports[`refractor elements SecuritySchemeElement should refract oauth2SecurityScheme variant 1`] = ` +(SecuritySchemeElement + (MemberElement + (StringElement) + (Oauth2SecuritySchemeElement + (MemberElement + (StringElement) + (OauthFlowsElement + (MemberElement + (StringElement) + (ClientCredentialsOAuthFlowElement + (MemberElement + (StringElement) + (StringElement)))))) + (MemberElement + (StringElement) + (StringElement))))) +`; + +exports[`refractor elements SecuritySchemeElement should refract openIdConnectSecurityScheme variant 1`] = ` +(SecuritySchemeElement + (MemberElement + (StringElement) + (OpenIdConnectSecuritySchemeElement + (MemberElement + (StringElement) + (StringElement))))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/index.ts new file mode 100644 index 0000000000..7942635840 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/SecurityScheme/index.ts @@ -0,0 +1,55 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { SecuritySchemeElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('SecuritySchemeElement', function () { + specify('should refract apiKeySecurityScheme variant', function () { + const element = SecuritySchemeElement.refract({ + apiKeySecurityScheme: { name: 'X-API-Key', location: 'header' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + + specify('should refract httpAuthSecurityScheme variant', function () { + const element = SecuritySchemeElement.refract({ + httpAuthSecurityScheme: { scheme: 'bearer', bearerFormat: 'JWT' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + + specify('should refract mtlsSecurityScheme variant', function () { + const element = SecuritySchemeElement.refract({ + mtlsSecurityScheme: { description: 'mTLS' }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + + specify('should refract oauth2SecurityScheme variant', function () { + const element = SecuritySchemeElement.refract({ + oauth2SecurityScheme: { + flows: { clientCredentials: { tokenUrl: 'https://idp.example/token' } }, + oauth2MetadataUrl: 'https://idp.example/.well-known', + }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + + specify('should refract openIdConnectSecurityScheme variant', function () { + const element = SecuritySchemeElement.refract({ + openIdConnectSecurityScheme: { + openIdConnectUrl: 'https://idp.example/.well-known/openid-configuration', + }, + }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..ef344fc919 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/__snapshots__/index.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements StringListElement should refract to semantic ApiDOM tree 1`] = ` +(StringListElement + (MemberElement + (StringElement) + (ArrayElement + (StringElement) + (StringElement)))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/index.ts b/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/index.ts new file mode 100644 index 0000000000..19c9503021 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/elements/StringList/index.ts @@ -0,0 +1,16 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { StringListElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('elements', function () { + context('StringListElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const element = StringListElement.refract({ list: ['read', 'write'] }); + + expect(sexprs(element)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/__snapshots__/index.ts.snap b/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..5934197a70 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/__snapshots__/index.ts.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor plugins replace-empty-element should leave non-empty values untouched 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentCapabilitiesElement + (MemberElement + (StringElement) + (BooleanElement))))) +`; + +exports[`refractor plugins replace-empty-element should refract securitySchemes object values into SecurityScheme elements 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (SecuritySchemeElement))))) +`; + +exports[`refractor plugins replace-empty-element should refract skills array items into AgentSkill elements 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement + (AgentSkillElement)))) +`; + +exports[`refractor plugins replace-empty-element should replace empty capabilities with an AgentCapabilitiesElement 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentCapabilitiesElement))) +`; + +exports[`refractor plugins replace-empty-element should replace empty provider with an AgentProviderElement 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentProviderElement))) +`; + +exports[`refractor plugins replace-empty-element should replace empty securitySchemes with a SecuritySchemesElement 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement))) +`; + +exports[`refractor plugins replace-empty-element should replace empty skills with a SkillsElement 1`] = ` +(A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement))) +`; diff --git a/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/index.ts b/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/index.ts new file mode 100644 index 0000000000..949bf87cfe --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/plugins/replace-empty-element/index.ts @@ -0,0 +1,115 @@ +import { expect } from 'chai'; +import dedent from 'dedent'; +import { sexprs } from '@swagger-api/apidom-core'; +import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2'; + +import { refractorPluginReplaceEmptyElement, AgentCardElement } from '../../../../src/index.ts'; + +describe('refractor', function () { + context('plugins', function () { + context('replace-empty-element', function () { + specify( + 'should replace empty capabilities with an AgentCapabilitiesElement', + async function () { + const yamlDefinition = dedent` + name: agent example + capabilities: + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }, + ); + + specify('should replace empty provider with an AgentProviderElement', async function () { + const yamlDefinition = dedent` + name: agent example + provider: + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }); + + specify('should replace empty skills with a SkillsElement', async function () { + const yamlDefinition = dedent` + name: agent example + skills: + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }); + + specify('should refract skills array items into AgentSkill elements', async function () { + const yamlDefinition = dedent` + name: agent example + skills: + - + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }); + + specify( + 'should replace empty securitySchemes with a SecuritySchemesElement', + async function () { + const yamlDefinition = dedent` + name: agent example + securitySchemes: + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }, + ); + + specify( + 'should refract securitySchemes object values into SecurityScheme elements', + async function () { + const yamlDefinition = dedent` + name: agent example + securitySchemes: + securityScheme1: + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }, + ); + + specify('should leave non-empty values untouched', async function () { + const yamlDefinition = dedent` + name: agent example + capabilities: + streaming: true + `; + const apiDOM = await parse(yamlDefinition); + const agentCardElement = AgentCardElement.refract(apiDOM.result, { + plugins: [refractorPluginReplaceEmptyElement()], + }); + + expect(sexprs(agentCardElement)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/refractor/real-world.ts b/packages/apidom-ns-a2a-1/test/refractor/real-world.ts new file mode 100644 index 0000000000..c21faacfd9 --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/refractor/real-world.ts @@ -0,0 +1,76 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { assert } from 'chai'; +import { toValue } from '@swagger-api/apidom-core'; + +import { + AgentCardElement, + isAgentCardElement, + isAgentCapabilitiesElement, + isSkillsElement, + isAgentSkillElement, +} from '../../src/index.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Round-trip a real published Agent Card (sourced from a2aproject/a2a-samples) + * through the namespace refractor and verify typed elements at each level. + * This exercises the foundation end-to-end on input that wasn't authored for + * the test suite. + */ +describe('refractor', function () { + context('real-world AgentCard', function () { + const source = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'real-world-planner-agent.json'), + 'utf-8', + ); + const raw = JSON.parse(source); + + specify('should refract to AgentCardElement', function () { + const element = AgentCardElement.refract(raw); + assert.isTrue(isAgentCardElement(element)); + }); + + specify('should produce typed scalar accessors', function () { + const element = AgentCardElement.refract(raw) as AgentCardElement; + assert.strictEqual(element.name?.toValue(), 'Langraph Planner Agent'); + assert.strictEqual(element.version?.toValue(), '1.0.0'); + }); + + specify('should refract capabilities into an AgentCapabilitiesElement', function () { + const element = AgentCardElement.refract(raw) as AgentCardElement; + assert.isTrue(isAgentCapabilitiesElement(element.capabilities)); + assert.strictEqual(element.capabilities?.streaming?.toValue(), true); + assert.strictEqual(element.capabilities?.pushNotifications?.toValue(), true); + }); + + specify('should refract skills into a SkillsElement of AgentSkillElements', function () { + const element = AgentCardElement.refract(raw) as AgentCardElement; + assert.isTrue(isSkillsElement(element.skills)); + const firstSkill = element.skills?.get(0); + assert.isTrue(isAgentSkillElement(firstSkill)); + assert.strictEqual(toValue(firstSkill?.get('id')), 'planner'); + assert.strictEqual(toValue(firstSkill?.get('name')), 'Task Planner'); + }); + + specify( + "should clone-through unknown fields (e.g. 'stateTransitionHistory' not in spec)", + function () { + // The real sample uses a stateTransitionHistory field that isn't in + // A2A v1's AgentCapabilities schema. The refractor should retain it + // as an untyped MemberElement rather than dropping or crashing. + const element = AgentCardElement.refract(raw) as AgentCardElement; + const stateTransition = element.capabilities?.get('stateTransitionHistory'); + assert.strictEqual(toValue(stateTransition), false); + }, + ); + + specify('should keep defaultInputModes / defaultOutputModes as arrays', function () { + const element = AgentCardElement.refract(raw) as AgentCardElement; + assert.deepEqual(toValue(element.defaultInputModes), ['text', 'text/plain']); + assert.deepEqual(toValue(element.defaultOutputModes), ['text', 'text/plain']); + }); + }); +}); diff --git a/packages/apidom-ns-a2a-1/test/tsconfig.json b/packages/apidom-ns-a2a-1/test/tsconfig.json new file mode 100644 index 0000000000..405aae2d2f --- /dev/null +++ b/packages/apidom-ns-a2a-1/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node" + }, + "include": [ + "." + ] +} diff --git a/packages/apidom-ns-a2a-1/tsconfig.declaration.json b/packages/apidom-ns-a2a-1/tsconfig.declaration.json new file mode 100644 index 0000000000..82d128fa80 --- /dev/null +++ b/packages/apidom-ns-a2a-1/tsconfig.declaration.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "types", + "noEmit": false, + "emitDeclarationOnly": true + } +} diff --git a/packages/apidom-ns-a2a-1/tsconfig.json b/packages/apidom-ns-a2a-1/tsconfig.json new file mode 100644 index 0000000000..5cc50cd885 --- /dev/null +++ b/packages/apidom-ns-a2a-1/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*" + ] +} diff --git a/packages/apidom-ns-a2a-1/vite.config.ts b/packages/apidom-ns-a2a-1/vite.config.ts new file mode 100644 index 0000000000..c950f6a368 --- /dev/null +++ b/packages/apidom-ns-a2a-1/vite.config.ts @@ -0,0 +1,8 @@ +import { createViteConfig } from '../../config/vite/vite.config.base.ts'; + +export default createViteConfig({ + packageName: 'apidom-ns-a2a-1', + libraryName: 'apidomNsA2A1', + entry: './src/index.ts', + chunkSizeWarningLimit: 1100, +}); diff --git a/packages/apidom-parser-adapter-a2a-json-1/.eslintignore b/packages/apidom-parser-adapter-a2a-json-1/.eslintignore new file mode 100644 index 0000000000..23853b909a --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/.eslintignore @@ -0,0 +1,8 @@ +/**/*.js +/**/*.mjs +/**/*.cjs +/dist +/types +/config +/.nyc_output +/node_modules diff --git a/packages/apidom-parser-adapter-a2a-json-1/.gitignore b/packages/apidom-parser-adapter-a2a-json-1/.gitignore new file mode 100644 index 0000000000..dd0ac50adc --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/.gitignore @@ -0,0 +1,7 @@ +/src/**/*.mjs +/src/**/*.cjs +/test/**/*.mjs +/dist +/types +/NOTICE +/swagger-api-apidom-parser-adapter-a2a-json-1-*.tgz diff --git a/packages/apidom-parser-adapter-a2a-json-1/.mocharc.json b/packages/apidom-parser-adapter-a2a-json-1/.mocharc.json new file mode 100644 index 0000000000..38ee8de7ec --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extensions": ["ts"], + "loader": "ts-node/esm", + "recursive": true, + "spec": "test/**/*.ts", + "file": ["test/mocha-bootstrap.ts"] +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/.npmrc b/packages/apidom-parser-adapter-a2a-json-1/.npmrc new file mode 100644 index 0000000000..4b82d2e7bb --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/.npmrc @@ -0,0 +1,2 @@ +save-prefix="=" +save=false diff --git a/packages/apidom-parser-adapter-a2a-json-1/CHANGELOG.md b/packages/apidom-parser-adapter-a2a-json-1/CHANGELOG.md new file mode 100644 index 0000000000..b8d6b03dd9 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/CHANGELOG.md @@ -0,0 +1,10 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 1.11.1 (2026-05-27) + +### Features + +- **a2a:** initial parser adapter for A2A v1 AgentCard documents in JSON format. Uses structural detection (`capabilities` + `skills` co-presence) since A2A has no version discriminator field. diff --git a/packages/apidom-parser-adapter-a2a-json-1/README.md b/packages/apidom-parser-adapter-a2a-json-1/README.md new file mode 100644 index 0000000000..655cb6d216 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/README.md @@ -0,0 +1,90 @@ +# @swagger-api/apidom-parser-adapter-a2a-json-1 + +`@swagger-api/apidom-parser-adapter-a2a-json-1` is a parser adapter for [A2A (Agent-to-Agent) Protocol v1.0](https://a2a-protocol.org/latest/specification/) AgentCard documents in [JSON format](https://www.json.org/json-en.html). +Under the hood this adapter uses [apidom-parser-adapter-json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-json) +to parse a source string into generic ApiDOM in [base ApiDOM namespace](https://github.com/swagger-api/apidom/tree/main/packages/apidom-core#base-namespace) +which is then refracted with [A2A 1.x.y Refractors](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-a2a-1#refractors). + +## Installation + +After [prerequisites](https://github.com/swagger-api/apidom/blob/main/README.md#prerequisites) for installing this package are satisfied, you can install it +via [npm CLI](https://docs.npmjs.com/cli) by running the following command: + +```sh + $ npm install @swagger-api/apidom-parser-adapter-a2a-json-1 +``` + +## Parser adapter API + +This parser adapter is fully compatible with parser adapter interface required by [@swagger-api/apidom-parser](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser#mounting-parser-adapters) +and implements all required properties. + +### mediaTypes + +Defines list of media types that this parser adapter recognizes. + +```js +[ + 'application/vnd.a2a;version=1.0.0', + 'application/vnd.a2a+json;version=1.0.0', +] +``` + +### detect + +A2A AgentCard documents have **no version discriminator field** (unlike OpenAPI's `"openapi": "3.1.0"` or Arazzo's `"arazzo": "1.0.1"`). [Detection](https://github.com/swagger-api/apidom/blob/main/packages/apidom-parser-adapter-a2a-json-1/src/adapter.ts) is therefore **structural**: a JSON document is treated as an A2A AgentCard when it parses as JSON and contains both a `capabilities` object and a `skills` array. False positives are possible — set the `mediaType` on the `File` explicitly when the type is known. + +### namespace + +This adapter exposes an instance of [A2A 1.x.y ApiDOM namespace](https://github.com/swagger-api/apidom/blob/main/packages/apidom-ns-a2a-1/README.md). + +### parse + +`parse` function consumes various options as a second argument. Here is a list of these options: + +Option | Type | Default | Description +--- | --- | --- | --- +`specObj` | `Object` | [Specification Object](https://github.com/swagger-api/apidom/blob/main/packages/apidom-ns-a2a-1/src/refractor/specification.ts) | This specification object drives the JSON AST transformation to A2A 1.x.y ApiDOM namespace. +`sourceMap` | `Boolean` | `false` | Indicate whether to generate source maps. +`refractorOpts` | `Object` | `{}` | Refractor options are passed to refractors during refracting phase. + +All unrecognized arbitrary options will be ignored. + +## Usage + +This parser adapter can be used directly or indirectly via [@swagger-api/apidom-parser](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser). + +### Direct usage + +During direct usage you don't need to provide `mediaType` as the `parse` function is already pre-bound +with [supported media types](#mediatypes). + +```js +import { parse, detect } from '@swagger-api/apidom-parser-adapter-a2a-json-1'; + +// detecting +await detect('{ "capabilities": {}, "skills": [] }'); // => true +await detect('test'); // => false + +// parsing +const parseResult = await parse('{ "capabilities": {}, "skills": [] }', { sourceMap: true }); +``` + +### Indirect usage + +You can omit the `mediaType` option here, but please read [Word on detect vs mediaTypes](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser#word-on-detect-vs-mediatypes) before you do so. + +```js +import ApiDOMParser from '@swagger-api/apidom-parser'; +import * as a2aJsonAdapter from '@swagger-api/apidom-parser-adapter-a2a-json-1'; + +const parser = new ApiDOMParser(); + +parser.use(a2aJsonAdapter); + +const parseResult = await parser.parse(jsonSource, { mediaType: a2aJsonAdapter.mediaTypes.latest('json') }); +``` + +## License + +Apache-2.0 diff --git a/packages/apidom-parser-adapter-a2a-json-1/config/api-extractor/api-extractor.json b/packages/apidom-parser-adapter-a2a-json-1/config/api-extractor/api-extractor.json new file mode 100644 index 0000000000..40bee5b261 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/config/api-extractor/api-extractor.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../../../api-extractor.json", + "mainEntryPointFilePath": "../../types/adapter.d.ts" +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/package.json b/packages/apidom-parser-adapter-a2a-json-1/package.json new file mode 100644 index 0000000000..9ea50b5137 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/package.json @@ -0,0 +1,58 @@ +{ + "name": "@swagger-api/apidom-parser-adapter-a2a-json-1", + "version": "1.11.1", + "description": "Parser adapter for parsing JSON documents into A2A v1 namespace.", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "type": "module", + "sideEffects": false, + "unpkg": "./dist/apidom-parser-adapter-a2a-json-1.browser.min.js", + "main": "./src/adapter.cjs", + "exports": { + "types": "./types/apidom-parser-adapter-a2a-json-1.d.ts", + "import": "./src/adapter.mjs", + "require": "./src/adapter.cjs" + }, + "types": "./types/apidom-parser-adapter-a2a-json-1.d.ts", + "scripts": { + "build": "npm run clean && run-p --max-parallel ${CPU_CORES:-6} typescript:declaration build:es build:cjs build:umd:browser", + "build:es": "cross-env BABEL_ENV=es babel src --out-dir src --extensions '.ts' --out-file-extension '.mjs' --root-mode 'upward'", + "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir src --extensions '.ts' --out-file-extension '.cjs' --root-mode 'upward'", + "build:umd:browser": "vite build", + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix", + "clean": "rimraf --glob 'src/**/*.mjs' 'src/**/*.cjs' ./dist ./types", + "typescript:check-types": "tsc --noEmit && tsc -p ./test/tsconfig.json --noEmit", + "typescript:declaration": "tsc -p tsconfig.declaration.json && api-extractor run -l -c ./config/api-extractor/api-extractor.json", + "test": "NODE_ENV=test ts-mocha --exit", + "prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .", + "postpack": "rimraf NOTICE LICENSES" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/swagger-api/apidom.git" + }, + "author": "SmartBear", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-json": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "files": [ + "src/**/*.mjs", + "src/**/*.cjs", + "dist/", + "types/apidom-parser-adapter-a2a-json-1.d.ts", + "LICENSES", + "NOTICE", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/src/adapter.ts b/packages/apidom-parser-adapter-a2a-json-1/src/adapter.ts new file mode 100644 index 0000000000..8d2a389f2a --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/src/adapter.ts @@ -0,0 +1,60 @@ +import { propOr, omit } from 'ramda'; +import { isNotUndefined } from 'ramda-adjunct'; +import { ParseResultElement, createNamespace } from '@swagger-api/apidom-core'; +import { parse as parseJSON, detect as detectJSON } from '@swagger-api/apidom-parser-adapter-json'; +import a2aNamespace, { AgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; + +export { default as mediaTypes } from './media-types.ts'; + +/** + * A2A AgentCard documents do not contain a version discriminator field + * (unlike `"openapi": "3.1.0"` or `"arazzo": "1.0.1"`). Detection is + * therefore *structural*: a JSON document is treated as an A2A AgentCard + * when it contains both a `capabilities` object and a `skills` array. These + * two markers together are distinctive enough to discriminate AgentCard + * documents from other JSON documents in practice. False positives are + * possible; use the `mediaType` field on `File` to override when known. + * + * The exported `detectionRegExp` matches either marker individually (it + * exists for parity with other adapters and is used as a cheap pre-filter). + * The `detect` function performs the full AND check. + * + * @public + */ +export const detectionRegExp = /"capabilities"\s*:\s*\{|"skills"\s*:\s*\[/; + +/** + * @public + */ +export const detect = async (source: string): Promise => { + if (!(await detectJSON(source))) return false; + const hasCapabilities = /"capabilities"\s*:\s*\{/.test(source); + const hasSkills = /"skills"\s*:\s*\[/.test(source); + return hasCapabilities && hasSkills; +}; + +/** + * @public + */ +export const parse = async ( + source: string, + options: Record = {}, +): Promise => { + const refractorOpts: Record = propOr({}, 'refractorOpts', options); + const parserOpts = omit(['refractorOpts'], options); + const parseResultElement = await parseJSON(source, parserOpts); + const { result } = parseResultElement; + + if (isNotUndefined(result)) { + const agentCardElement = AgentCardElement.refract(result, refractorOpts); + agentCardElement.classes.push('result'); + parseResultElement.replaceResult(agentCardElement); + } + + return parseResultElement; +}; + +/** + * @public + */ +export const namespace = createNamespace(a2aNamespace); diff --git a/packages/apidom-parser-adapter-a2a-json-1/src/media-types.ts b/packages/apidom-parser-adapter-a2a-json-1/src/media-types.ts new file mode 100644 index 0000000000..ec497c3dc3 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/src/media-types.ts @@ -0,0 +1,11 @@ +import { mediaTypes, A2AMediaTypes } from '@swagger-api/apidom-ns-a2a-1'; + +/** + * @public + */ +const jsonMediaTypes = new A2AMediaTypes( + ...mediaTypes.filterByFormat('generic'), + ...mediaTypes.filterByFormat('json'), +); + +export default jsonMediaTypes; diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/.eslintrc b/packages/apidom-parser-adapter-a2a-json-1/test/.eslintrc new file mode 100644 index 0000000000..c47eea4f48 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/.eslintrc @@ -0,0 +1,55 @@ +{ + "env": { + "mocha": true + }, + "globals": { + "document": true + }, + "plugins": [ + "mocha" + ], + "rules": { + "no-void": 0, + "func-names": 0, + "prefer-arrow-callback": 0, + "no-array-constructor": 0, + "prefer-rest-params": 0, + "no-new-wrappers": 0, + "mocha/no-skipped-tests": 2, + "mocha/handle-done-callback": 2, + "mocha/valid-suite-description": 2, + "mocha/no-mocha-arrows": 2, + "mocha/no-hooks-for-single-case": 2, + "mocha/no-sibling-hooks": 2, + "mocha/no-top-level-hooks": 2, + "mocha/no-identical-title": 2, + "mocha/no-nested-tests": 2, + "mocha/no-exclusive-tests": 2, + "no-underscore-dangle": 0, + "import/no-relative-packages": 0, + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "UPPER_CASE"], + "leadingUnderscore": "forbid" + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__dirname$", + "match": true + } + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__filename$", + "match": true + } + } + ] + } +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/__snapshots__/adapter.ts.snap b/packages/apidom-parser-adapter-a2a-json-1/test/__snapshots__/adapter.ts.snap new file mode 100644 index 0000000000..6750117e0b --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/__snapshots__/adapter.ts.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`adapter should parse 1`] = ` +(ParseResultElement + (A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentProviderElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))) + (MemberElement + (StringElement) + (AgentCapabilitiesElement + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (AgentSkillElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement + (StringElement) + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement)))))) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (SecuritySchemeElement + (MemberElement + (StringElement) + (ApiKeySecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))))))) + (MemberElement + (StringElement) + (ArrayElement + (SecurityRequirementElement + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringListElement + (MemberElement + (StringElement) + (ArrayElement))))))))))) +`; diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/adapter.ts b/packages/apidom-parser-adapter-a2a-json-1/test/adapter.ts new file mode 100644 index 0000000000..2a3af5ce2e --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/adapter.ts @@ -0,0 +1,83 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { assert, expect } from 'chai'; +import { isParseResultElement, sexprs } from '@swagger-api/apidom-core'; +import { isAgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; + +import * as adapter from '../src/adapter.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const jsonSpec = fs + .readFileSync(path.join(__dirname, 'fixtures', 'sample-agent-card.json')) + .toString(); + +describe('adapter', function () { + context('given AgentCard definition in JSON format', function () { + specify('should detect as A2A AgentCard', async function () { + assert.isTrue(await adapter.detect(jsonSpec)); + }); + + specify('should detect when both capabilities and skills are present', async function () { + assert.isTrue(await adapter.detect('{"capabilities": {}, "skills": []}')); + }); + + specify('should NOT detect when only capabilities is present', async function () { + assert.isFalse(await adapter.detect('{"capabilities": {}}')); + }); + + specify('should NOT detect when only skills is present', async function () { + assert.isFalse(await adapter.detect('{"skills": []}')); + }); + + specify('should NOT detect an OpenAPI document', async function () { + assert.isFalse(await adapter.detect('{"openapi": "3.1.0", "info": {}, "paths": {}}')); + }); + }); + + it('should parse', async function () { + const parseResult = await adapter.parse(jsonSpec, { sourceMap: true }); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + expect(sexprs(parseResult)).toMatchSnapshot(); + }); + + context('given zero byte empty file', function () { + specify('should return empty parse result', async function () { + const parseResult = await adapter.parse('', { sourceMap: true }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given non-zero byte empty file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' ', { sourceMap: true }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('given invalid json file', function () { + specify('should return empty parser result', async function () { + const parseResult = await adapter.parse(' a ', { sourceMap: true }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('detectionRegExp', function () { + specify('should match a document with capabilities object', function () { + assert.isTrue(adapter.detectionRegExp.test('{"capabilities": {}}')); + }); + + specify('should match a document with skills array', function () { + assert.isTrue(adapter.detectionRegExp.test('{"skills": []}')); + }); + + specify('should not match unrelated content', function () { + assert.isFalse(adapter.detectionRegExp.test('{"foo": "bar"}')); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/fixtures/sample-agent-card.json b/packages/apidom-parser-adapter-a2a-json-1/test/fixtures/sample-agent-card.json new file mode 100644 index 0000000000..20cf2b55f0 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/fixtures/sample-agent-card.json @@ -0,0 +1,44 @@ +{ + "name": "Recipe Agent", + "description": "Helps users find and follow recipes", + "url": "https://recipes.example.com/a2a", + "version": "1.0.0", + "provider": { + "organization": "Example Foods", + "url": "https://example.com" + }, + "capabilities": { + "streaming": true, + "pushNotifications": false, + "extendedAgentCard": false + }, + "defaultInputModes": ["text/plain"], + "defaultOutputModes": ["application/json"], + "skills": [ + { + "id": "find-recipe", + "name": "Find Recipe", + "description": "Locate recipes matching ingredients or cuisine.", + "tags": ["recipes", "search"], + "examples": ["Find a vegan pasta recipe"], + "inputModes": ["text/plain"], + "outputModes": ["application/json"] + } + ], + "securitySchemes": { + "apiKey": { + "apiKeySecurityScheme": { + "name": "X-API-Key", + "location": "header", + "description": "API key authentication" + } + } + }, + "securityRequirements": [ + { + "schemes": { + "apiKey": { "list": [] } + } + } + ] +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/media-types.ts b/packages/apidom-parser-adapter-a2a-json-1/test/media-types.ts new file mode 100644 index 0000000000..622bfc64cc --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/media-types.ts @@ -0,0 +1,33 @@ +import { assert } from 'chai'; +import ApiDOMParser from '@swagger-api/apidom-parser'; + +import * as a2aJsonAdapter from '../src/adapter.ts'; + +describe('given adapter is used in parser', function () { + const parser = new ApiDOMParser().use(a2aJsonAdapter); + + /** + * Note: A2A has no version-discriminator field in the document, so the + * detection regex has no `version_json` / `version_yaml` named groups. As a + * result `ApiDOMParser.findMediaType` resolves to the generic media type + * rather than the format-specific one. Consumers that need format-specific + * routing should set the `mediaType` on the `File` explicitly. + */ + context('given A2A AgentCard in JSON format', function () { + specify('should resolve to the generic A2A media type', async function () { + const mediaType = await parser.findMediaType( + '{"capabilities": {"streaming": true}, "skills": [], "name": "x", "url": "https://x", "version": "1.0.0"}', + ); + + assert.strictEqual(mediaType, 'application/vnd.a2a;version=1.0.1'); + }); + }); + + context('given a non-A2A JSON document', function () { + specify('should not match A2A media type', async function () { + const mediaType = await parser.findMediaType('{"foo": "bar"}'); + + assert.notStrictEqual(mediaType, 'application/vnd.a2a;version=1.0.0'); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/mocha-bootstrap.ts b/packages/apidom-parser-adapter-a2a-json-1/test/mocha-bootstrap.ts new file mode 100644 index 0000000000..aec560d03f --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/mocha-bootstrap.ts @@ -0,0 +1,11 @@ +import * as chai from 'chai'; +import { jestSnapshotPlugin, addSerializer } from 'mocha-chai-jest-snapshot'; + +// @ts-ignore +import * as jestApiDOMSerializer from '../../../scripts/jest-serializer-apidom.mjs'; +// @ts-ignore +import * as jestStringSerializer from '../../../scripts/jest-serializer-string.mjs'; + +chai.use(jestSnapshotPlugin()); +addSerializer(jestApiDOMSerializer); +addSerializer(jestStringSerializer); diff --git a/packages/apidom-parser-adapter-a2a-json-1/test/tsconfig.json b/packages/apidom-parser-adapter-a2a-json-1/test/tsconfig.json new file mode 100644 index 0000000000..405aae2d2f --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node" + }, + "include": [ + "." + ] +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/tsconfig.declaration.json b/packages/apidom-parser-adapter-a2a-json-1/tsconfig.declaration.json new file mode 100644 index 0000000000..82d128fa80 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/tsconfig.declaration.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "types", + "noEmit": false, + "emitDeclarationOnly": true + } +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/tsconfig.json b/packages/apidom-parser-adapter-a2a-json-1/tsconfig.json new file mode 100644 index 0000000000..5cc50cd885 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*" + ] +} diff --git a/packages/apidom-parser-adapter-a2a-json-1/vite.config.ts b/packages/apidom-parser-adapter-a2a-json-1/vite.config.ts new file mode 100644 index 0000000000..e1e5c92b1e --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-json-1/vite.config.ts @@ -0,0 +1,8 @@ +import { createViteConfig } from '../../config/vite/vite.config.base.ts'; + +export default createViteConfig({ + packageName: 'apidom-parser-adapter-a2a-json-1', + libraryName: 'apidomParserAdapterA2AJson1', + entry: './src/adapter.ts', + chunkSizeWarningLimit: 1100, +}); diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/.eslintignore b/packages/apidom-parser-adapter-a2a-yaml-1/.eslintignore new file mode 100644 index 0000000000..23853b909a --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/.eslintignore @@ -0,0 +1,8 @@ +/**/*.js +/**/*.mjs +/**/*.cjs +/dist +/types +/config +/.nyc_output +/node_modules diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/.gitignore b/packages/apidom-parser-adapter-a2a-yaml-1/.gitignore new file mode 100644 index 0000000000..5bab635f3a --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/.gitignore @@ -0,0 +1,7 @@ +/src/**/*.mjs +/src/**/*.cjs +/test/**/*.mjs +/dist +/types +/NOTICE +/swagger-api-apidom-parser-adapter-a2a-yaml-1-*.tgz diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/.mocharc.json b/packages/apidom-parser-adapter-a2a-yaml-1/.mocharc.json new file mode 100644 index 0000000000..38ee8de7ec --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extensions": ["ts"], + "loader": "ts-node/esm", + "recursive": true, + "spec": "test/**/*.ts", + "file": ["test/mocha-bootstrap.ts"] +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/.npmrc b/packages/apidom-parser-adapter-a2a-yaml-1/.npmrc new file mode 100644 index 0000000000..4b82d2e7bb --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/.npmrc @@ -0,0 +1,2 @@ +save-prefix="=" +save=false diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/CHANGELOG.md b/packages/apidom-parser-adapter-a2a-yaml-1/CHANGELOG.md new file mode 100644 index 0000000000..d844d4cc11 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/CHANGELOG.md @@ -0,0 +1,10 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 1.11.1 (2026-05-27) + +### Features + +- **a2a:** initial parser adapter for A2A v1 AgentCard documents in YAML 1.2 format. Uses structural detection (`capabilities` + `skills` co-presence) since A2A has no version discriminator field. diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/README.md b/packages/apidom-parser-adapter-a2a-yaml-1/README.md new file mode 100644 index 0000000000..3f21bc1f6a --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/README.md @@ -0,0 +1,90 @@ +# @swagger-api/apidom-parser-adapter-a2a-yaml-1 + +`@swagger-api/apidom-parser-adapter-a2a-yaml-1` is a parser adapter for [A2A (Agent-to-Agent) Protocol v1.0](https://a2a-protocol.org/latest/specification/) AgentCard documents in [YAML format](https://yaml.org/spec/1.2/spec.html). +Under the hood this adapter uses [apidom-parser-adapter-yaml-1-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-yaml-1-2) +to parse a source string into generic ApiDOM in [base ApiDOM namespace](https://github.com/swagger-api/apidom/tree/main/packages/apidom-core#base-namespace) +which is then refracted with [A2A 1.x.y Refractors](https://github.com/swagger-api/apidom/tree/main/packages/apidom-ns-a2a-1#refractors). + +## Installation + +After [prerequisites](https://github.com/swagger-api/apidom/blob/main/README.md#prerequisites) for installing this package are satisfied, you can install it +via [npm CLI](https://docs.npmjs.com/cli) by running the following command: + +```sh + $ npm install @swagger-api/apidom-parser-adapter-a2a-yaml-1 +``` + +## Parser adapter API + +This parser adapter is fully compatible with parser adapter interface required by [@swagger-api/apidom-parser](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser#mounting-parser-adapters) +and implements all required properties. + +### mediaTypes + +Defines list of media types that this parser adapter recognizes. + +```js +[ + 'application/vnd.a2a;version=1.0.0', + 'application/vnd.a2a+yaml;version=1.0.0', +] +``` + +### detect + +A2A AgentCard documents have **no version discriminator field** (unlike OpenAPI's `"openapi": "3.1.0"` or Arazzo's `"arazzo": "1.0.1"`). [Detection](https://github.com/swagger-api/apidom/blob/main/packages/apidom-parser-adapter-a2a-yaml-1/src/adapter.ts) is therefore **structural**: a YAML document is treated as an A2A AgentCard when it parses as YAML and contains both a `capabilities` mapping and a `skills` sequence. False positives are possible — set the `mediaType` on the `File` explicitly when the type is known. + +### namespace + +This adapter exposes an instance of [A2A 1.x.y ApiDOM namespace](https://github.com/swagger-api/apidom/blob/main/packages/apidom-ns-a2a-1/README.md). + +### parse + +`parse` function consumes various options as a second argument. Here is a list of these options: + +Option | Type | Default | Description +--- | --- | --- | --- +`specObj` | `Object` | [Specification Object](https://github.com/swagger-api/apidom/blob/main/packages/apidom-ns-a2a-1/src/refractor/specification.ts) | This specification object drives the YAML AST transformation to A2A 1.x.y ApiDOM namespace. +`sourceMap` | `Boolean` | `false` | Indicate whether to generate source maps. +`refractorOpts` | `Object` | `{}` | Refractor options are passed to refractors during refracting phase. + +All unrecognized arbitrary options will be ignored. + +## Usage + +This parser adapter can be used directly or indirectly via [@swagger-api/apidom-parser](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser). + +### Direct usage + +During direct usage you don't need to provide `mediaType` as the `parse` function is already pre-bound +with [supported media types](#mediatypes). + +```js +import { parse, detect } from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; + +// detecting +await detect('capabilities:\n streaming: true\nskills: []'); // => true +await detect('test'); // => false + +// parsing +const parseResult = await parse('capabilities:\n streaming: true\nskills: []', { sourceMap: true }); +``` + +### Indirect usage + +You can omit the `mediaType` option here, but please read [Word on detect vs mediaTypes](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser#word-on-detect-vs-mediatypes) before you do so. + +```js +import ApiDOMParser from '@swagger-api/apidom-parser'; +import * as a2aYamlAdapter from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; + +const parser = new ApiDOMParser(); + +parser.use(a2aYamlAdapter); + +const parseResult = await parser.parse(yamlSource, { mediaType: a2aYamlAdapter.mediaTypes.latest('yaml') }); +``` + +## License + +Apache-2.0 diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/config/api-extractor/api-extractor.json b/packages/apidom-parser-adapter-a2a-yaml-1/config/api-extractor/api-extractor.json new file mode 100644 index 0000000000..40bee5b261 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/config/api-extractor/api-extractor.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../../../api-extractor.json", + "mainEntryPointFilePath": "../../types/adapter.d.ts" +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/package.json b/packages/apidom-parser-adapter-a2a-yaml-1/package.json new file mode 100644 index 0000000000..5ca2052369 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/package.json @@ -0,0 +1,58 @@ +{ + "name": "@swagger-api/apidom-parser-adapter-a2a-yaml-1", + "version": "1.11.1", + "description": "Parser adapter for parsing YAML documents into A2A v1 namespace.", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "type": "module", + "sideEffects": false, + "unpkg": "./dist/apidom-parser-adapter-a2a-yaml-1.browser.min.js", + "main": "./src/adapter.cjs", + "exports": { + "types": "./types/apidom-parser-adapter-a2a-yaml-1.d.ts", + "import": "./src/adapter.mjs", + "require": "./src/adapter.cjs" + }, + "types": "./types/apidom-parser-adapter-a2a-yaml-1.d.ts", + "scripts": { + "build": "npm run clean && run-p --max-parallel ${CPU_CORES:-6} typescript:declaration build:es build:cjs build:umd:browser", + "build:es": "cross-env BABEL_ENV=es babel src --out-dir src --extensions '.ts' --out-file-extension '.mjs' --root-mode 'upward'", + "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir src --extensions '.ts' --out-file-extension '.cjs' --root-mode 'upward'", + "build:umd:browser": "vite build", + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix", + "clean": "rimraf --glob 'src/**/*.mjs' 'src/**/*.cjs' ./dist ./types", + "typescript:check-types": "tsc --noEmit && tsc -p ./test/tsconfig.json --noEmit", + "typescript:declaration": "tsc -p tsconfig.declaration.json && api-extractor run -l -c ./config/api-extractor/api-extractor.json", + "test": "NODE_ENV=test ts-mocha --exit", + "prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .", + "postpack": "rimraf NOTICE LICENSES" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/swagger-api/apidom.git" + }, + "author": "SmartBear", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.11.1", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.11.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "files": [ + "src/**/*.mjs", + "src/**/*.cjs", + "dist/", + "types/apidom-parser-adapter-a2a-yaml-1.d.ts", + "LICENSES", + "NOTICE", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/src/adapter.ts b/packages/apidom-parser-adapter-a2a-yaml-1/src/adapter.ts new file mode 100644 index 0000000000..0c41a0c810 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/src/adapter.ts @@ -0,0 +1,64 @@ +import { propOr, omit } from 'ramda'; +import { isNotUndefined } from 'ramda-adjunct'; +import { ParseResultElement, createNamespace } from '@swagger-api/apidom-core'; +import { + parse as parseYAML, + detect as detectYAML, +} from '@swagger-api/apidom-parser-adapter-yaml-1-2'; +import a2aNamespace, { AgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; + +export { default as mediaTypes } from './media-types.ts'; + +/** + * A2A AgentCard documents do not contain a version discriminator field + * (unlike `openapi: 3.1.0` or `arazzo: 1.0.1`). Detection is therefore + * *structural*: a YAML or JSON document is treated as an A2A AgentCard when + * it contains both a `capabilities` mapping and a `skills` sequence. These + * two markers together are distinctive enough to discriminate AgentCard + * documents from other YAML/JSON documents in practice. False positives are + * possible; use the `mediaType` field on `File` to override when known. + * + * The exported `detectionRegExp` matches either marker individually (used as + * a cheap pre-filter). The `detect` function performs the full AND check. + * + * @public + */ +export const detectionRegExp = + /(?^(["']?)(?:capabilities|skills)\2\s*:)|(?"capabilities"\s*:\s*\{|"skills"\s*:\s*\[)/m; + +/** + * @public + */ +export const detect = async (source: string): Promise => { + if (!(await detectYAML(source))) return false; + const hasCapabilities = + /^(["']?)capabilities\1\s*:/m.test(source) || /"capabilities"\s*:\s*\{/.test(source); + const hasSkills = /^(["']?)skills\1\s*:/m.test(source) || /"skills"\s*:\s*\[/.test(source); + return hasCapabilities && hasSkills; +}; + +/** + * @public + */ +export const parse = async ( + source: string, + options: Record = {}, +): Promise => { + const refractorOpts: Record = propOr({}, 'refractorOpts', options); + const parserOpts = omit(['refractorOpts'], options); + const parseResultElement = await parseYAML(source, parserOpts); + const { result } = parseResultElement; + + if (isNotUndefined(result)) { + const agentCardElement = AgentCardElement.refract(result, refractorOpts); + agentCardElement.classes.push('result'); + parseResultElement.replaceResult(agentCardElement); + } + + return parseResultElement; +}; + +/** + * @public + */ +export const namespace = createNamespace(a2aNamespace); diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/src/media-types.ts b/packages/apidom-parser-adapter-a2a-yaml-1/src/media-types.ts new file mode 100644 index 0000000000..eb0ff09b78 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/src/media-types.ts @@ -0,0 +1,11 @@ +import { mediaTypes, A2AMediaTypes } from '@swagger-api/apidom-ns-a2a-1'; + +/** + * @public + */ +const yamlMediaTypes = new A2AMediaTypes( + ...mediaTypes.filterByFormat('generic'), + ...mediaTypes.filterByFormat('yaml'), +); + +export default yamlMediaTypes; diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/.eslintrc b/packages/apidom-parser-adapter-a2a-yaml-1/test/.eslintrc new file mode 100644 index 0000000000..c47eea4f48 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/.eslintrc @@ -0,0 +1,55 @@ +{ + "env": { + "mocha": true + }, + "globals": { + "document": true + }, + "plugins": [ + "mocha" + ], + "rules": { + "no-void": 0, + "func-names": 0, + "prefer-arrow-callback": 0, + "no-array-constructor": 0, + "prefer-rest-params": 0, + "no-new-wrappers": 0, + "mocha/no-skipped-tests": 2, + "mocha/handle-done-callback": 2, + "mocha/valid-suite-description": 2, + "mocha/no-mocha-arrows": 2, + "mocha/no-hooks-for-single-case": 2, + "mocha/no-sibling-hooks": 2, + "mocha/no-top-level-hooks": 2, + "mocha/no-identical-title": 2, + "mocha/no-nested-tests": 2, + "mocha/no-exclusive-tests": 2, + "no-underscore-dangle": 0, + "import/no-relative-packages": 0, + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "UPPER_CASE"], + "leadingUnderscore": "forbid" + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__dirname$", + "match": true + } + }, + { + "selector": "variable", + "format": null, + "filter": { + "regex": "^__filename$", + "match": true + } + } + ] + } +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/__snapshots__/adapter.ts.snap b/packages/apidom-parser-adapter-a2a-yaml-1/test/__snapshots__/adapter.ts.snap new file mode 100644 index 0000000000..6750117e0b --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/__snapshots__/adapter.ts.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`adapter should parse 1`] = ` +(ParseResultElement + (A2aAgentCard1Element + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (AgentProviderElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))) + (MemberElement + (StringElement) + (AgentCapabilitiesElement + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement)))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (AgentSkillElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ArrayElement + (StringElement) + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement))) + (MemberElement + (StringElement) + (ArrayElement + (StringElement)))))) + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (SecuritySchemeElement + (MemberElement + (StringElement) + (ApiKeySecuritySchemeElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)))))))) + (MemberElement + (StringElement) + (ArrayElement + (SecurityRequirementElement + (MemberElement + (StringElement) + (ObjectElement + (MemberElement + (StringElement) + (StringListElement + (MemberElement + (StringElement) + (ArrayElement))))))))))) +`; diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/adapter.ts b/packages/apidom-parser-adapter-a2a-yaml-1/test/adapter.ts new file mode 100644 index 0000000000..a72240825b --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/adapter.ts @@ -0,0 +1,71 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { assert, expect } from 'chai'; +import { isParseResultElement, sexprs } from '@swagger-api/apidom-core'; +import { isAgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; + +import * as adapter from '../src/adapter.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const yamlSpec = fs + .readFileSync(path.join(__dirname, 'fixtures', 'sample-agent-card.yaml')) + .toString(); + +describe('adapter', function () { + context('given AgentCard definition in YAML 1.2 format', function () { + specify('should detect as A2A AgentCard', async function () { + assert.isTrue(await adapter.detect(yamlSpec)); + }); + + specify('should detect minimal YAML with both keys', async function () { + assert.isTrue(await adapter.detect('capabilities: {}\nskills: []\n')); + }); + + specify('should NOT detect when only capabilities is present', async function () { + assert.isFalse(await adapter.detect('capabilities: {}\n')); + }); + + specify('should NOT detect an Arazzo workflow', async function () { + assert.isFalse(await adapter.detect('arazzo: 1.0.1\ninfo:\n title: x\n version: 1.0\n')); + }); + }); + + context('given AgentCard definition in JSON format (YAML 1.2 is a superset)', function () { + specify('should also detect JSON', async function () { + const jsonSrc = + '{"capabilities": {"streaming": true}, "skills": [], "name": "test", "url": "https://x", "version": "1.0.0"}'; + assert.isTrue(await adapter.detect(jsonSrc)); + }); + }); + + it('should parse', async function () { + const parseResult = await adapter.parse(yamlSpec, { sourceMap: true }); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + expect(sexprs(parseResult)).toMatchSnapshot(); + }); + + context('given zero byte empty file', function () { + specify('should return empty parse result', async function () { + const parseResult = await adapter.parse('', { sourceMap: true }); + + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('detectionRegExp', function () { + specify('should match a YAML document with capabilities key', function () { + assert.isTrue(adapter.detectionRegExp.test('capabilities:\n streaming: true\n')); + }); + + specify('should match a YAML document with skills key', function () { + assert.isTrue(adapter.detectionRegExp.test('skills:\n - id: a\n')); + }); + + specify('should match a JSON document with capabilities object', function () { + assert.isTrue(adapter.detectionRegExp.test('{"capabilities": {}}')); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/fixtures/sample-agent-card.yaml b/packages/apidom-parser-adapter-a2a-yaml-1/test/fixtures/sample-agent-card.yaml new file mode 100644 index 0000000000..c0551caa35 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/fixtures/sample-agent-card.yaml @@ -0,0 +1,38 @@ +name: Recipe Agent +description: Helps users find and follow recipes +url: https://recipes.example.com/a2a +version: 1.0.0 +provider: + organization: Example Foods + url: https://example.com +capabilities: + streaming: true + pushNotifications: false + extendedAgentCard: false +defaultInputModes: + - text/plain +defaultOutputModes: + - application/json +skills: + - id: find-recipe + name: Find Recipe + description: Locate recipes matching ingredients or cuisine. + tags: + - recipes + - search + examples: + - Find a vegan pasta recipe + inputModes: + - text/plain + outputModes: + - application/json +securitySchemes: + apiKey: + apiKeySecurityScheme: + name: X-API-Key + location: header + description: API key authentication +securityRequirements: + - schemes: + apiKey: + list: [] diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/media-types.ts b/packages/apidom-parser-adapter-a2a-yaml-1/test/media-types.ts new file mode 100644 index 0000000000..f13b0ba8a9 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/media-types.ts @@ -0,0 +1,23 @@ +import { assert } from 'chai'; +import ApiDOMParser from '@swagger-api/apidom-parser'; + +import * as a2aYamlAdapter from '../src/adapter.ts'; + +describe('given adapter is used in parser', function () { + const parser = new ApiDOMParser().use(a2aYamlAdapter); + + /** + * Note: A2A has no version-discriminator field. See the JSON adapter's + * test/media-types.ts for the rationale; the generic media type is the + * expected resolution. + */ + context('given A2A AgentCard in YAML format', function () { + specify('should resolve to the generic A2A media type', async function () { + const mediaType = await parser.findMediaType( + 'capabilities:\n streaming: true\nskills: []\nname: x\nurl: https://x\nversion: 1.0.0\n', + ); + + assert.strictEqual(mediaType, 'application/vnd.a2a;version=1.0.1'); + }); + }); +}); diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/mocha-bootstrap.ts b/packages/apidom-parser-adapter-a2a-yaml-1/test/mocha-bootstrap.ts new file mode 100644 index 0000000000..aec560d03f --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/mocha-bootstrap.ts @@ -0,0 +1,11 @@ +import * as chai from 'chai'; +import { jestSnapshotPlugin, addSerializer } from 'mocha-chai-jest-snapshot'; + +// @ts-ignore +import * as jestApiDOMSerializer from '../../../scripts/jest-serializer-apidom.mjs'; +// @ts-ignore +import * as jestStringSerializer from '../../../scripts/jest-serializer-string.mjs'; + +chai.use(jestSnapshotPlugin()); +addSerializer(jestApiDOMSerializer); +addSerializer(jestStringSerializer); diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/test/tsconfig.json b/packages/apidom-parser-adapter-a2a-yaml-1/test/tsconfig.json new file mode 100644 index 0000000000..405aae2d2f --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node" + }, + "include": [ + "." + ] +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.declaration.json b/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.declaration.json new file mode 100644 index 0000000000..82d128fa80 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.declaration.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "types", + "noEmit": false, + "emitDeclarationOnly": true + } +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.json b/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.json new file mode 100644 index 0000000000..5cc50cd885 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*" + ] +} diff --git a/packages/apidom-parser-adapter-a2a-yaml-1/vite.config.ts b/packages/apidom-parser-adapter-a2a-yaml-1/vite.config.ts new file mode 100644 index 0000000000..df396112a7 --- /dev/null +++ b/packages/apidom-parser-adapter-a2a-yaml-1/vite.config.ts @@ -0,0 +1,8 @@ +import { createViteConfig } from '../../config/vite/vite.config.base.ts'; + +export default createViteConfig({ + packageName: 'apidom-parser-adapter-a2a-yaml-1', + libraryName: 'apidomParserAdapterA2AYaml1', + entry: './src/adapter.ts', + chunkSizeWarningLimit: 1100, +}); diff --git a/packages/apidom-reference/package.json b/packages/apidom-reference/package.json index b62a348ae9..4de5b6a9ac 100644 --- a/packages/apidom-reference/package.json +++ b/packages/apidom-reference/package.json @@ -123,6 +123,16 @@ "require": "./src/parse/parsers/arazzo-yaml-1/index.cjs", "types": "./types/parse/parsers/arazzo-yaml-1/index.d.ts" }, + "./parse/parsers/a2a-json-1": { + "import": "./src/parse/parsers/a2a-json-1/index.mjs", + "require": "./src/parse/parsers/a2a-json-1/index.cjs", + "types": "./types/parse/parsers/a2a-json-1/index.d.ts" + }, + "./parse/parsers/a2a-yaml-1": { + "import": "./src/parse/parsers/a2a-yaml-1/index.mjs", + "require": "./src/parse/parsers/a2a-yaml-1/index.cjs", + "types": "./types/parse/parsers/a2a-yaml-1/index.d.ts" + }, "./parse/parsers/apidom-json": { "import": "./src/parse/parsers/apidom-json/index.mjs", "require": "./src/parse/parsers/apidom-json/index.cjs", @@ -313,12 +323,15 @@ }, "optionalDependencies": { "@swagger-api/apidom-json-pointer": "^1.11.2", + "@swagger-api/apidom-ns-a2a-1": "^1.11.1", "@swagger-api/apidom-ns-arazzo-1": "^1.11.2", "@swagger-api/apidom-ns-asyncapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-2": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-0": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-1": "^1.11.2", "@swagger-api/apidom-ns-openapi-3-2": "^1.11.2", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.11.1", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.11.1", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.11.2", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.11.2", "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.11.2", @@ -340,6 +353,7 @@ }, "devDependencies": { "@swagger-api/apidom-json-pointer": "*", + "@swagger-api/apidom-ns-a2a-1": "*", "@swagger-api/apidom-ns-arazzo-1": "*", "@swagger-api/apidom-ns-asyncapi-2": "*", "@swagger-api/apidom-ns-openapi-2": "*", @@ -348,6 +362,8 @@ "@swagger-api/apidom-ns-openapi-3-2": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "*", + "@swagger-api/apidom-parser-adapter-a2a-json-1": "*", + "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "*", "@swagger-api/apidom-parser-adapter-arazzo-json-1": "*", "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "*", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "*", diff --git a/packages/apidom-reference/src/configuration/saturated.ts b/packages/apidom-reference/src/configuration/saturated.ts index 76cf3e237f..b6c4ad32fd 100644 --- a/packages/apidom-reference/src/configuration/saturated.ts +++ b/packages/apidom-reference/src/configuration/saturated.ts @@ -22,6 +22,8 @@ import AsyncAPIYAML2Parser from '../parse/parsers/asyncapi-yaml-2/index.ts'; import AsyncAPIYAML3Parser from '../parse/parsers/asyncapi-yaml-3/index.ts'; import ArazzoJSON1Parser from '../parse/parsers/arazzo-json-1/index.ts'; import ArazzoYAML1Parser from '../parse/parsers/arazzo-yaml-1/index.ts'; +import A2AJSON1Parser from '../parse/parsers/a2a-json-1/index.ts'; +import A2AYAML1Parser from '../parse/parsers/a2a-yaml-1/index.ts'; import APIDOMJSONParser from '../parse/parsers/apidom-json/index.ts'; import JSONParser from '../parse/parsers/json/index.ts'; import YAMLParser from '../parse/parsers/yaml-1-2/index.ts'; @@ -52,6 +54,8 @@ options.parse.parsers = [ new AsyncAPIYAML3Parser({ allowEmpty: true, sourceMap: false }), new ArazzoJSON1Parser({ allowEmpty: true, sourceMap: false }), new ArazzoYAML1Parser({ allowEmpty: true, sourceMap: false }), + new A2AJSON1Parser({ allowEmpty: true, sourceMap: false }), + new A2AYAML1Parser({ allowEmpty: true, sourceMap: false }), new APIDesignSystemsJSONParser({ allowEmpty: true, sourceMap: false }), new APIDesignSystemsYAMLParser({ allowEmpty: true, sourceMap: false }), new APIDOMJSONParser({ allowEmpty: true, sourceMap: false }), diff --git a/packages/apidom-reference/src/parse/parsers/a2a-json-1/index.ts b/packages/apidom-reference/src/parse/parsers/a2a-json-1/index.ts new file mode 100644 index 0000000000..fbe02178ae --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/a2a-json-1/index.ts @@ -0,0 +1,60 @@ +import { pick } from 'ramda'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { + parse, + mediaTypes as A2A1JsonMediaTypes, + detect, +} from '@swagger-api/apidom-parser-adapter-a2a-json-1'; + +import ParserError from '../../../errors/ParserError.ts'; +import Parser, { ParserOptions } from '../Parser.ts'; +import File from '../../../File.ts'; + +export type { default as Parser, ParserOptions } from '../Parser.ts'; +export type { default as File, FileOptions } from '../../../File.ts'; + +/** + * @public + */ +export interface A2AJSON1ParserOptions extends Omit {} + +/** + * @public + */ +class A2AJSON1Parser extends Parser { + public syntacticAnalysis?: 'direct' | 'indirect'; + + public refractorOpts!: object; + + constructor(options?: A2AJSON1ParserOptions) { + const { fileExtensions = [], mediaTypes = A2A1JsonMediaTypes, ...rest } = options ?? {}; + + super({ ...rest, name: 'a2a-json-1', fileExtensions, mediaTypes }); + } + + async canParse(file: File): Promise { + const hasSupportedFileExtension = + this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension); + const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType); + + if (!hasSupportedFileExtension) return false; + if (hasSupportedMediaType) return true; + if (!hasSupportedMediaType) { + return detect(file.toString()); + } + return false; + } + + async parse(file: File): Promise { + const source = file.toString(); + + try { + const parserOpts = pick(['sourceMap', 'syntacticAnalysis', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: unknown) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + } +} + +export default A2AJSON1Parser; diff --git a/packages/apidom-reference/src/parse/parsers/a2a-yaml-1/index.ts b/packages/apidom-reference/src/parse/parsers/a2a-yaml-1/index.ts new file mode 100644 index 0000000000..f4b8d98ac3 --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/a2a-yaml-1/index.ts @@ -0,0 +1,58 @@ +import { pick } from 'ramda'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { + parse, + mediaTypes as A2A1YamlMediaTypes, + detect, +} from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; + +import ParserError from '../../../errors/ParserError.ts'; +import Parser, { ParserOptions } from '../Parser.ts'; +import File from '../../../File.ts'; + +export type { default as Parser, ParserOptions } from '../Parser.ts'; +export type { default as File, FileOptions } from '../../../File.ts'; + +/** + * @public + */ +export interface A2AYAML1ParserOptions extends Omit {} + +/** + * @public + */ +class A2AYAML1Parser extends Parser { + public refractorOpts!: object; + + constructor(options?: A2AYAML1ParserOptions) { + const { fileExtensions = [], mediaTypes = A2A1YamlMediaTypes, ...rest } = options ?? {}; + + super({ ...rest, name: 'a2a-yaml-1', fileExtensions, mediaTypes }); + } + + async canParse(file: File): Promise { + const hasSupportedFileExtension = + this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension); + const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType); + + if (!hasSupportedFileExtension) return false; + if (hasSupportedMediaType) return true; + if (!hasSupportedMediaType) { + return detect(file.toString()); + } + return false; + } + + async parse(file: File): Promise { + const source = file.toString(); + + try { + const parserOpts = pick(['sourceMap', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: unknown) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + } +} + +export default A2AYAML1Parser; diff --git a/packages/apidom-reference/test/parse/parsers/a2a-json-1/fixtures/sample-agent-card.json b/packages/apidom-reference/test/parse/parsers/a2a-json-1/fixtures/sample-agent-card.json new file mode 100644 index 0000000000..20cf2b55f0 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/a2a-json-1/fixtures/sample-agent-card.json @@ -0,0 +1,44 @@ +{ + "name": "Recipe Agent", + "description": "Helps users find and follow recipes", + "url": "https://recipes.example.com/a2a", + "version": "1.0.0", + "provider": { + "organization": "Example Foods", + "url": "https://example.com" + }, + "capabilities": { + "streaming": true, + "pushNotifications": false, + "extendedAgentCard": false + }, + "defaultInputModes": ["text/plain"], + "defaultOutputModes": ["application/json"], + "skills": [ + { + "id": "find-recipe", + "name": "Find Recipe", + "description": "Locate recipes matching ingredients or cuisine.", + "tags": ["recipes", "search"], + "examples": ["Find a vegan pasta recipe"], + "inputModes": ["text/plain"], + "outputModes": ["application/json"] + } + ], + "securitySchemes": { + "apiKey": { + "apiKeySecurityScheme": { + "name": "X-API-Key", + "location": "header", + "description": "API key authentication" + } + } + }, + "securityRequirements": [ + { + "schemes": { + "apiKey": { "list": [] } + } + } + ] +} diff --git a/packages/apidom-reference/test/parse/parsers/a2a-json-1/index.ts b/packages/apidom-reference/test/parse/parsers/a2a-json-1/index.ts new file mode 100644 index 0000000000..00a5611eba --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/a2a-json-1/index.ts @@ -0,0 +1,170 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { assert } from 'chai'; +import { isParseResultElement, hasElementSourceMap } from '@swagger-api/apidom-core'; +import { isAgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; +import { mediaTypes } from '@swagger-api/apidom-parser-adapter-a2a-json-1'; + +import A2AJSON1Parser from '../../../../src/parse/parsers/a2a-json-1/index.ts'; +import File from '../../../../src/File.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +describe('parsers', function () { + context('A2AJSON1Parser', function () { + context('canParse', function () { + context('given file with .json extension', function () { + context('and with proper media type', function () { + specify('should return true', async function () { + const file1 = new File({ + uri: '/path/to/agent.json', + mediaType: mediaTypes.latest('generic'), + }); + const file2 = new File({ + uri: '/path/to/agent.json', + mediaType: mediaTypes.latest('json'), + }); + const parser = new A2AJSON1Parser(); + + assert.isTrue(await parser.canParse(file1)); + assert.isTrue(await parser.canParse(file2)); + }); + }); + + context('and with improper media type', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent.json', + mediaType: 'application/vnd.aai.asyncapi+json;version=2.6.0', + }); + const parser = new A2AJSON1Parser(); + + assert.isFalse(await parser.canParse(file)); + }); + }); + }); + + context('given file with unknown extension', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent.yaml', + mediaType: mediaTypes.latest('json'), + }); + const parser = new A2AJSON1Parser({ fileExtensions: ['.json'] }); + + assert.isFalse(await parser.canParse(file)); + }); + }); + + context('given file with no extension', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent', + mediaType: mediaTypes.latest('json'), + }); + const parser = new A2AJSON1Parser({ fileExtensions: ['.json'] }); + + assert.isFalse(await parser.canParse(file)); + }); + }); + + context('given file with supported extension', function () { + context('and file data is buffer and can be detected as A2A AgentCard', function () { + specify('should return true', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const file = new File({ + uri: '/path/to/agent.json', + data: fs.readFileSync(uri), + }); + const parser = new A2AJSON1Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + + context('and file data is string and can be detected as A2A AgentCard', function () { + specify('should return true', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const file = new File({ + uri: '/path/to/agent.json', + data: fs.readFileSync(uri).toString(), + }); + const parser = new A2AJSON1Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + }); + }); + + context('parse', function () { + context('given A2A AgentCard JSON data', function () { + specify('should return parse result containing an AgentCardElement', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('json') }); + const parser = new A2AJSON1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + }); + }); + + context('given A2A AgentCard JSON data as buffer', function () { + specify('should return parse result', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const data = fs.readFileSync(uri); + const file = new File({ uri, data, mediaType: mediaTypes.latest('json') }); + const parser = new A2AJSON1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + }); + }); + + context('given empty file', function () { + specify('should return empty parse result', async function () { + const file = new File({ + uri: '/path/to/file.json', + data: '', + mediaType: mediaTypes.latest('json'), + }); + const parser = new A2AJSON1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('sourceMap', function () { + context('given sourceMap enabled', function () { + specify('should decorate ApiDOM with source maps', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('json') }); + const parser = new A2AJSON1Parser({ sourceMap: true }); + const parseResult = await parser.parse(file); + + assert.isTrue(hasElementSourceMap(parseResult.api)); + }); + }); + + context('given sourceMap disabled', function () { + specify('should not decorate ApiDOM with source maps', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.json'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('json') }); + const parser = new A2AJSON1Parser(); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.api?.meta.get('sourceMap')); + }); + }); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/fixtures/sample-agent-card.yaml b/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/fixtures/sample-agent-card.yaml new file mode 100644 index 0000000000..c0551caa35 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/fixtures/sample-agent-card.yaml @@ -0,0 +1,38 @@ +name: Recipe Agent +description: Helps users find and follow recipes +url: https://recipes.example.com/a2a +version: 1.0.0 +provider: + organization: Example Foods + url: https://example.com +capabilities: + streaming: true + pushNotifications: false + extendedAgentCard: false +defaultInputModes: + - text/plain +defaultOutputModes: + - application/json +skills: + - id: find-recipe + name: Find Recipe + description: Locate recipes matching ingredients or cuisine. + tags: + - recipes + - search + examples: + - Find a vegan pasta recipe + inputModes: + - text/plain + outputModes: + - application/json +securitySchemes: + apiKey: + apiKeySecurityScheme: + name: X-API-Key + location: header + description: API key authentication +securityRequirements: + - schemes: + apiKey: + list: [] diff --git a/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/index.ts b/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/index.ts new file mode 100644 index 0000000000..f07f9a7135 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/a2a-yaml-1/index.ts @@ -0,0 +1,170 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { assert } from 'chai'; +import { isParseResultElement, hasElementSourceMap } from '@swagger-api/apidom-core'; +import { isAgentCardElement } from '@swagger-api/apidom-ns-a2a-1'; +import { mediaTypes } from '@swagger-api/apidom-parser-adapter-a2a-yaml-1'; + +import A2AYAML1Parser from '../../../../src/parse/parsers/a2a-yaml-1/index.ts'; +import File from '../../../../src/File.ts'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +describe('parsers', function () { + context('A2AYAML1Parser', function () { + context('canParse', function () { + context('given file with .yaml extension', function () { + context('and with proper media type', function () { + specify('should return true', async function () { + const file1 = new File({ + uri: '/path/to/agent.yaml', + mediaType: mediaTypes.latest('generic'), + }); + const file2 = new File({ + uri: '/path/to/agent.yaml', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new A2AYAML1Parser(); + + assert.isTrue(await parser.canParse(file1)); + assert.isTrue(await parser.canParse(file2)); + }); + }); + + context('and with improper media type', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent.yaml', + mediaType: 'application/vnd.aai.asyncapi;version=2.6.0', + }); + const parser = new A2AYAML1Parser(); + + assert.isFalse(await parser.canParse(file)); + }); + }); + }); + + context('given file with unknown extension', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent.json', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new A2AYAML1Parser({ fileExtensions: ['.yaml', '.yml'] }); + + assert.isFalse(await parser.canParse(file)); + }); + }); + + context('given file with no extension', function () { + specify('should return false', async function () { + const file = new File({ + uri: '/path/to/agent', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new A2AYAML1Parser({ fileExtensions: ['.yaml', '.yml'] }); + + assert.isFalse(await parser.canParse(file)); + }); + }); + + context('given file with supported extension', function () { + context('and file data is buffer and can be detected as A2A AgentCard', function () { + specify('should return true', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const file = new File({ + uri: '/path/to/agent.yaml', + data: fs.readFileSync(uri), + }); + const parser = new A2AYAML1Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + + context('and file data is string and can be detected as A2A AgentCard', function () { + specify('should return true', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const file = new File({ + uri: '/path/to/agent.yaml', + data: fs.readFileSync(uri).toString(), + }); + const parser = new A2AYAML1Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + }); + }); + + context('parse', function () { + context('given A2A AgentCard YAML data', function () { + specify('should return parse result containing an AgentCardElement', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('yaml') }); + const parser = new A2AYAML1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + }); + }); + + context('given A2A AgentCard YAML data as buffer', function () { + specify('should return parse result', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const data = fs.readFileSync(uri); + const file = new File({ uri, data, mediaType: mediaTypes.latest('yaml') }); + const parser = new A2AYAML1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(isAgentCardElement(parseResult.api)); + }); + }); + + context('given empty file', function () { + specify('should return empty parse result', async function () { + const file = new File({ + uri: '/path/to/file.yaml', + data: '', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = new A2AYAML1Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('sourceMap', function () { + context('given sourceMap enabled', function () { + specify('should decorate ApiDOM with source maps', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('yaml') }); + const parser = new A2AYAML1Parser({ sourceMap: true }); + const parseResult = await parser.parse(file); + + assert.isTrue(hasElementSourceMap(parseResult.api)); + }); + }); + + context('given sourceMap disabled', function () { + specify('should not decorate ApiDOM with source maps', async function () { + const uri = path.join(__dirname, 'fixtures', 'sample-agent-card.yaml'); + const data = fs.readFileSync(uri).toString(); + const file = new File({ uri, data, mediaType: mediaTypes.latest('yaml') }); + const parser = new A2AYAML1Parser(); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.api?.meta.get('sourceMap')); + }); + }); + }); + }); + }); +});