From 7241a7cb956228803a0117e9389e3546601e04f3 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 00:11:36 +0600 Subject: [PATCH 1/9] organize imports with prettifier --- .prettierrc | 24 +++- .vscode/settings.json | 17 ++- eslint.config.mjs | 32 +----- package.json | 6 +- pnpm-lock.yaml | 163 ++++++++++++++++++++++++++++ src/roles/openai/chat.validation.ts | 2 +- src/roles/openapi/OpenAPI.role.ts | 7 +- src/types/declarations/express.d.ts | 1 - 8 files changed, 205 insertions(+), 47 deletions(-) diff --git a/.prettierrc b/.prettierrc index f02f5a3..8cebcaa 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,22 @@ { - "tabWidth": 4, - "useTabs": false, - "singleQuote": true, - "printWidth": 150 + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 140, + "tabWidth": 4, + "plugins": ["@ianvs/prettier-plugin-sort-imports"], + "importOrder": [ + "^node:(.*)$", + "", + "", + "", + "", + "^@smythos/(.*)$", + "", + "^@/(.*)$", + "", + "^[./]" + ], + "importOrderParserPlugins": ["typescript", "decorators-legacy"], + "importOrderTypeScriptVersion": "5.0.0" } diff --git a/.vscode/settings.json b/.vscode/settings.json index b827180..8d8e063 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,13 +2,20 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" + "source.organizeImports": "never" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - } - } + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "prettier.enable": true, + "prettier.requireConfig": true, + "prettier.configPath": ".prettierrc", + "prettier.resolveGlobalModules": true, + "prettier.prettierPath": "./node_modules/prettier" } diff --git a/eslint.config.mjs b/eslint.config.mjs index 1718de9..d073dbf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,36 +16,8 @@ export default tseslint.config( import: importPlugin, }, rules: { - 'import/order': [ - 'error', - { - groups: [ - 'builtin', - 'external', - 'internal', - ['parent', 'sibling'], - 'index', - ], - 'newlines-between': 'always', - pathGroups: [ - { - pattern: '@smythos/**', - group: 'internal', - position: 'before', - }, - { - pattern: '@/**', - group: 'internal', - position: 'after', - }, - ], - pathGroupsExcludedImportTypes: ['builtin'], - alphabetize: { - order: 'asc', - caseInsensitive: true, - }, - }, - ], + // Import ordering is handled by Prettier plugin + 'import/order': 'off', 'import/no-unresolved': 'off', '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-explicit-any': 'warn', diff --git a/package.json b/package.json index 725d709..d9264c6 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,12 @@ "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist/types --rootDir src -p tsconfig.json", "build:rollup": "rollup -c ./rollup.config.js", "build": "pnpm run clean && pnpm run build:rollup && pnpm run build:types && npm link", - "lint": "eslint src/**/*.ts", - "lint:fix": "eslint src/**/*.ts --fix" + "lint": "pnpm exec eslint \"src/**/*.ts\"", + "lint:fix": "pnpm exec eslint \"src/**/*.ts\" --fix" }, "devDependencies": { "@eslint/js": "^9.39.1", + "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@rollup/plugin-json": "^6.1.0", "@types/express": "^4.17.21", "@types/node": "^22.9.0", @@ -40,6 +41,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", + "prettier": "^3.6.2", "rimraf": "^6.0.1", "rollup": "^2.79.2", "rollup-plugin-esbuild": "^6.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61bcdc7..ab3ae71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: '@eslint/js': specifier: ^9.39.1 version: 9.39.1 + '@ianvs/prettier-plugin-sort-imports': + specifier: ^4.7.0 + version: 4.7.0(prettier@3.6.2) '@rollup/plugin-json': specifier: ^6.1.0 version: 6.1.0(rollup@2.79.2) @@ -63,6 +66,9 @@ importers: eslint-plugin-import: specifier: ^2.32.0 version: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1) + prettier: + specifier: ^3.6.2 + version: 3.6.2 rimraf: specifier: ^6.0.1 version: 6.1.0 @@ -275,10 +281,47 @@ packages: resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@borewit/text-codec@0.1.1': resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} @@ -563,6 +606,24 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@ianvs/prettier-plugin-sort-imports@4.7.0': + resolution: {integrity: sha512-soa2bPUJAFruLL4z/CnMfSEKGznm5ebz29fIa9PxYtu8HHyLKNE1NXAs6dylfw1jn/ilEIfO2oLLN6uAafb7DA==} + peerDependencies: + '@prettier/plugin-oxc': ^0.0.4 + '@vue/compiler-sfc': 2.7.x || 3.x + content-tag: ^4.0.0 + prettier: 2 || 3 || ^4.0.0-0 + prettier-plugin-ember-template-tag: ^2.1.0 + peerDependenciesMeta: + '@prettier/plugin-oxc': + optional: true + '@vue/compiler-sfc': + optional: true + content-tag: + optional: true + prettier-plugin-ember-template-tag: + optional: true + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -587,6 +648,19 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@modelcontextprotocol/sdk@1.22.0': resolution: {integrity: sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==} engines: {node: '>=18'} @@ -2594,6 +2668,9 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -2602,6 +2679,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -3105,6 +3187,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4567,8 +4652,55 @@ snapshots: '@aws/lambda-invoke-store@0.1.1': {} + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@borewit/text-codec@0.1.1': {} '@colors/colors@1.6.0': {} @@ -4784,6 +4916,17 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@ianvs/prettier-plugin-sort-imports@4.7.0(prettier@3.6.2)': + dependencies: + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + prettier: 3.6.2 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + '@inquirer/external-editor@1.0.3(@types/node@22.19.1)': dependencies: chardet: 2.1.1 @@ -4808,6 +4951,20 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@modelcontextprotocol/sdk@1.22.0': dependencies: ajv: 8.17.1 @@ -7308,6 +7465,8 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + js-tokens@4.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -7316,6 +7475,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-bigint@1.0.0: dependencies: bignumber.js: 9.3.1 @@ -7836,6 +7997,8 @@ snapshots: pend@1.2.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.3: {} diff --git a/src/roles/openai/chat.validation.ts b/src/roles/openai/chat.validation.ts index 3c34d73..1e2b677 100644 --- a/src/roles/openai/chat.validation.ts +++ b/src/roles/openai/chat.validation.ts @@ -14,7 +14,7 @@ export const chatValidations = { Joi.object({ role: Joi.string().valid('system', 'user', 'assistant').required(), content: Joi.string().allow(null).required(), - }) + }), ) .required(), model: Joi.string().required(), diff --git a/src/roles/openapi/OpenAPI.role.ts b/src/roles/openapi/OpenAPI.role.ts index b00caaf..5a7c23b 100644 --- a/src/roles/openapi/OpenAPI.role.ts +++ b/src/roles/openapi/OpenAPI.role.ts @@ -3,8 +3,7 @@ import express from 'express'; import { ConnectorService } from '@smythos/sdk/core'; import AgentLoader from '@/middlewares/AgentLoader.mw'; - -import { BaseRole } from '../Base.role'; +import { BaseRole } from '@/roles/Base.role'; export class OpenAPIRole extends BaseRole { /** @@ -25,7 +24,7 @@ export class OpenAPIRole extends BaseRole { } async function openapiJSONHandler(req: express.Request, res: express.Response) { - let domain = req.hostname; + const domain = req.hostname; const agentData = req._agentData; const agentDataConnector = ConnectorService.getAgentDataConnector(); @@ -43,7 +42,7 @@ async function openapiJSONHandler(req: express.Request, res: express.Response) { } async function openapiJSON4LLMHandler(req: express.Request, res: express.Response) { - let domain = req.hostname; + const domain = req.hostname; const agentData = req._agentData; const agentDataConnector = ConnectorService.getAgentDataConnector(); diff --git a/src/types/declarations/express.d.ts b/src/types/declarations/express.d.ts index 63ede5c..1e65e33 100644 --- a/src/types/declarations/express.d.ts +++ b/src/types/declarations/express.d.ts @@ -12,4 +12,3 @@ declare global { } export {}; - From 9b333c98fb94b9a33ae4fea1fc2e07ee1e308dc4 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 00:15:02 +0600 Subject: [PATCH 2/9] resolve role options with generic method - BaseRole.resolve() --- src/roles/Base.role.ts | 53 +++++++++++++++++++++++++++++-- src/roles/chatgpt/ChatGPT.role.ts | 6 ++-- src/roles/openai/OpenAI.role.ts | 26 ++++++--------- src/roles/postman/Postman.role.ts | 29 ++++++++++------- src/roles/swagger/Swagger.role.ts | 7 ++-- 5 files changed, 86 insertions(+), 35 deletions(-) diff --git a/src/roles/Base.role.ts b/src/roles/Base.role.ts index 3d419b8..70ab153 100644 --- a/src/roles/Base.role.ts +++ b/src/roles/Base.role.ts @@ -1,6 +1,15 @@ import express from 'express'; -import { SRE } from '@smythos/sdk/core'; +/** + * Generic type for values that can be either static or dynamically resolved via a function. + * @template T - The type of the resolved value + * @template Args - The argument object type for the resolver function + * + * @example + * type ModelResolver = Resolvable }>; + * type ServerOriginResolver = Resolvable; + */ +export type Resolvable = T | ((args: Args) => T); export class BaseRole { /** @@ -8,7 +17,47 @@ export class BaseRole { * @param router - The router to mount the role on. * @param middlewares - The custom middlewares to apply to the role on top of the default middlewares. */ - constructor(protected middlewares: express.RequestHandler[], protected options?: Record) {} + constructor( + protected middlewares: express.RequestHandler[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected options?: Record, + ) {} + /** + * Resolves a value that can be either static or dynamically computed via a function. + * This generic method implements the DRY principle for value-or-function resolution patterns. + * + * @template T - The type of the resolved value + * @template Args - The argument object type for the resolver function + * + * @param resolvable - Either a static value of type T or a function that returns T + * @param options - Configuration object + * @param options.args - Argument object to pass to the resolver function (if resolvable is a function) + * @param options.defaultValue - Optional default value to use if resolvable is undefined or returns undefined + * @returns The resolved value of type T, or defaultValue, or undefined + * + * @example + * -- Resolve server origin without default (returns string | undefined) + * const origin = this.resolve(this.options.serverOrigin, { args: { req } }); + * + * @example + * -- Resolve model with default fallback (always returns string) + * const model = this.resolve(this.options.model, { + * args: { baseModel, planInfo: agentData?.planInfo || {} }, + * defaultValue: baseModel + * }); + */ + protected resolve( + resolvable: Resolvable | undefined, + options: { args: Args; defaultValue?: T }, + ): T | undefined { + if (resolvable === undefined) { + return options.defaultValue; + } + const resolved = typeof resolvable === 'function' ? (resolvable as (args: Args) => T)(options.args) : resolvable; + return resolved !== undefined ? resolved : options.defaultValue; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async mount(router: express.Router) {} } diff --git a/src/roles/chatgpt/ChatGPT.role.ts b/src/roles/chatgpt/ChatGPT.role.ts index 30ed46c..3b2c962 100644 --- a/src/roles/chatgpt/ChatGPT.role.ts +++ b/src/roles/chatgpt/ChatGPT.role.ts @@ -33,11 +33,11 @@ export class ChatGPTRole extends BaseRole { public async mount(router: express.Router) { const middlewares = [AgentLoader, ...this.middlewares]; - router.get('/api-docs/openapi-gpt.json', middlewares, async (req: any, res) => { - const agentData = (req as any)._agentData; + router.get('/api-docs/openapi-gpt.json', middlewares, async (req: express.Request, res: express.Response) => { + const agentData = req._agentData; // Resolve server origin from options (static value or dynamic function) - const serverOrigin = typeof this.options.serverOrigin === 'function' ? this.options.serverOrigin(req) : this.options.serverOrigin; + const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); // Fetch the base OpenAPI 3.0.1 specification from agent data const agentDataConnector = ConnectorService.getAgentDataConnector(); diff --git a/src/roles/openai/OpenAI.role.ts b/src/roles/openai/OpenAI.role.ts index d83b15d..3cc7d4d 100644 --- a/src/roles/openai/OpenAI.role.ts +++ b/src/roles/openai/OpenAI.role.ts @@ -3,17 +3,16 @@ import { Readable } from 'stream'; import express from 'express'; import APIError from '@/APIError.class'; -import { DEFAULT_AGENT_MODEL_SETTINGS_KEY, DEFAULT_AGENT_MODEL } from '@/constants'; +import { DEFAULT_AGENT_MODEL, DEFAULT_AGENT_MODEL_SETTINGS_KEY } from '@/constants'; import AgentLoader from '@/middlewares/AgentLoader.mw'; import { BaseRole } from '@/roles/Base.role'; -import { chatService } from '@/services/chat.service'; +import { chatService } from '@/roles/openai/Chat.service'; +import type { ModelResolver } from '@/types/resolvers.types'; import { chatValidations } from './chat.validation'; import AgentDataAdapter from './middlewares/AgentDataAdapter.mw'; import { validate } from './middlewares/Validate.mw'; -import { extractBearerToken, createOpenAIError } from './utils'; - -type ModelResolver = string | ((baseModel: string, planInfo: Record) => string); +import { createOpenAIError, extractBearerToken } from './utils'; export class OpenAIRole extends BaseRole { /** @@ -44,18 +43,11 @@ export class OpenAIRole extends BaseRole { // Get base model from agent settings or use system default const baseModel = agentSettings?.get(DEFAULT_AGENT_MODEL_SETTINGS_KEY) || DEFAULT_AGENT_MODEL; - // Apply model resolution strategy - let model: string; - if (typeof this.options?.model === 'function') { - // Dynamic: resolve model using custom function with base model and plan info - model = this.options.model(baseModel, agentData?.planInfo || {}); - } else if (this.options?.model) { - // Static: use the specified model override - model = this.options.model; - } else { - // Default: use base model from agent settings - model = baseModel; - } + // Apply model resolution strategy: static string, dynamic function, or default to base model + const model = this.resolve(this.options?.model, { + args: { baseModel, planInfo: agentData?.planInfo || {} }, + defaultValue: baseModel, + }); const authHeader = req.headers['authorization']; const apiKey = extractBearerToken(authHeader); diff --git a/src/roles/postman/Postman.role.ts b/src/roles/postman/Postman.role.ts index ebddf81..d63b344 100644 --- a/src/roles/postman/Postman.role.ts +++ b/src/roles/postman/Postman.role.ts @@ -6,6 +6,12 @@ import { ConnectorService } from '@smythos/sdk/core'; import AgentLoader from '@/middlewares/AgentLoader.mw'; import { BaseRole } from '@/roles/Base.role'; +interface PostmanConversionResult { + result: boolean; + reason?: string; + output?: Array<{ data: unknown }>; +} + export class PostmanRole extends BaseRole { /** * Creates a new PostmanRole instance. @@ -34,23 +40,24 @@ export class PostmanRole extends BaseRole { public async mount(router: express.Router) { const middlewares = [AgentLoader, ...this.middlewares]; - router.get('/', middlewares, async (req: any, res) => { - const domain = req.hostname; - const agentData = (req as any)._agentData; + router.get('/', middlewares, async (req: express.Request, res: express.Response) => { + const agentData = req._agentData; try { - const serverOrigin = typeof this.options.serverOrigin === 'function' ? this.options.serverOrigin(req) : this.options.serverOrigin; + const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); const agentDataConnector = ConnectorService.getAgentDataConnector(); - const openAPISpec = await agentDataConnector.getOpenAPIJSON(agentData, serverOrigin, agentData.version, false).catch((error) => { - console.error(error); - return { error: error.message }; - }); + const openAPISpec = await agentDataConnector + .getOpenAPIJSON(agentData, serverOrigin, agentData.version, false) + .catch((error) => { + console.error(error); + return { error: error.message }; + }); if (openAPISpec?.error) { return res.status(500).send({ error: openAPISpec.error }); } - const conversionResult: any = await new Promise((resolve, reject) => { + const conversionResult: PostmanConversionResult = await new Promise((resolve, reject) => { Converter.convert({ type: 'json', data: openAPISpec }, {}, (err, result) => { if (err) { reject(err); @@ -67,9 +74,9 @@ export class PostmanRole extends BaseRole { res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(conversionResult?.output?.[0]?.data, null, 2)); - } catch (error: any) { + } catch (error: unknown) { console.error(error); - return res.status(500).send({ error: error.message }); + return res.status(500).send({ error: (error as Error).message }); } }); } diff --git a/src/roles/swagger/Swagger.role.ts b/src/roles/swagger/Swagger.role.ts index 18a768a..e6e6d6c 100644 --- a/src/roles/swagger/Swagger.role.ts +++ b/src/roles/swagger/Swagger.role.ts @@ -16,7 +16,10 @@ export class SwaggerRole extends BaseRole { * - staticPath: The path to the static files for the role. this assumes that a static route is mounted and the swagger files (swagger.js, swagger-debug.js) are served from this path. * Defaults to '/static/embodiment/swagger'. */ - constructor(middlewares: express.RequestHandler[], options: { serverOrigin: string | ((req: express.Request) => string); staticPath?: string }) { + constructor( + middlewares: express.RequestHandler[], + options: { serverOrigin: string | ((req: express.Request) => string); staticPath?: string }, + ) { super(middlewares, { staticPath: '/static/embodiment/swagger', ...options, @@ -32,7 +35,7 @@ export class SwaggerRole extends BaseRole { const isTestDomain = agentData.usingTestDomain; //const openApiDocument = await getOpenAPIJSON(agentData, domain, req._agentVersion, false); - const serverOrigin = typeof this.options.serverOrigin === 'function' ? this.options.serverOrigin(req) : this.options.serverOrigin; + const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); const agentDataConnector = ConnectorService.getAgentDataConnector(); const openApiDocument = await agentDataConnector.getOpenAPIJSON(agentData, serverOrigin, agentData.version, false); From 44486f30a8d39f049665f65aeb34a3138f2d7dfb Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 00:17:52 +0600 Subject: [PATCH 3/9] add resolver types file --- src/types/resolvers.types.ts | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/types/resolvers.types.ts diff --git a/src/types/resolvers.types.ts b/src/types/resolvers.types.ts new file mode 100644 index 0000000..52b8369 --- /dev/null +++ b/src/types/resolvers.types.ts @@ -0,0 +1,46 @@ +import type express from 'express'; + +import type { Resolvable } from '@/roles/Base.role'; + +/** + * Common resolver types for role configuration. + * These types define patterns for resolving dynamic values in role options. + */ + +/** + * Resolves AI model selection based on agent configuration. + * Can be a static model name or a function that dynamically selects based on context. + * + * @example + * -- Static model + * const options = { model: 'gpt-4' }; + * + * @example + * -- Dynamic model selection + * const options = { + * model: ({ baseModel, planInfo }) => { + * if (planInfo.tier === 'enterprise') return 'gpt-4-turbo'; + * return baseModel; + * } + * }; + */ +export type ModelResolver = Resolvable }>; + +/** + * Resolves server origin URL based on request context. + * Can be a static URL or a function that dynamically determines the origin. + * + * @example + * -- Static origin + * const options = { serverOrigin: 'https://api.example.com' }; + * + * @example + * -- Dynamic origin based on request + * const options = { + * serverOrigin: ({ req }) => { + * const host = req.get('host'); + * return `https://${host}`; + * } + * }; + */ +export type ServerOriginResolver = Resolvable; From 26e93c0d65cae8fd22c97225fac1b61e1ca07386 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 00:19:23 +0600 Subject: [PATCH 4/9] implement Alexa Role --- src/index.ts | 5 +- src/roles/alexa/Alexa.role.ts | 82 ++++++++++ src/roles/alexa/Alexa.service.ts | 250 +++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 src/roles/alexa/Alexa.role.ts create mode 100644 src/roles/alexa/Alexa.service.ts diff --git a/src/index.ts b/src/index.ts index 26237be..388b359 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,14 +2,11 @@ export { version } from '../package.json'; - - - export * from './APIError.class'; export * from './constants'; export * from './middlewares/AgentLoader.mw'; export * from './roles/Base.role'; -export * from './services/chat.service'; +export * from './roles/openai/Chat.service'; export * from './utils/agent.utils'; export * from './roles/chatgpt/ChatGPT.role'; export * from './roles/openai/chat.validation'; diff --git a/src/roles/alexa/Alexa.role.ts b/src/roles/alexa/Alexa.role.ts new file mode 100644 index 0000000..8db79ce --- /dev/null +++ b/src/roles/alexa/Alexa.role.ts @@ -0,0 +1,82 @@ +import express from 'express'; + +import { DEFAULT_AGENT_MODEL, DEFAULT_AGENT_MODEL_SETTINGS_KEY } from '@/constants'; +import AgentLoader from '@/middlewares/AgentLoader.mw'; +import { BaseRole } from '@/roles/Base.role'; +import type { ModelResolver, ServerOriginResolver } from '@/types/resolvers.types'; + +import { createAlexaSkill, handleAlexaRequest, isAlexaEnabled, parseAlexaRequest } from './Alexa.service'; + +export class OpenAPIRole extends BaseRole { + /** + * Creates a new OpenAPIRole instance. + * @param middlewares - The custom middlewares to apply to the role on top of the default middlewares. + * @param options - The options for the role + * @param options.serverOrigin - Server origin URL: string for static, or function to resolve dynamically from request + * @param options.model - Optional model override: string for static model, or function to resolve model dynamically + */ + constructor(middlewares: express.RequestHandler[] = [], options: { serverOrigin: ServerOriginResolver; model?: ModelResolver }) { + super(middlewares, options); + } + + public async mount(router: express.Router) { + const middlewares = [AgentLoader, ...this.middlewares]; + + router.post('/', middlewares, async (req: express.Request, res: express.Response) => { + try { + const agentData = req._agentData; + const agentSettings = req._agentSettings; + await agentSettings?.ready(); + const isEnabled = isAlexaEnabled(agentData, agentSettings); + // wait for agent embodiments to be ready + await agentSettings?.embodiments?.ready(); + + // Resolve server origin: static string or dynamic from request + const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); + + const alexRequest = parseAlexaRequest(req.body); + + // Get base model from agent settings or use system default + const baseModel = agentSettings?.get(DEFAULT_AGENT_MODEL_SETTINGS_KEY) || DEFAULT_AGENT_MODEL; + + // Apply model resolution strategy: static string, dynamic function, or default to base model + const model = this.resolve(this.options?.model, { + args: { baseModel, planInfo: agentData?.planInfo || {} }, + defaultValue: baseModel, + }); + + const response = await handleAlexaRequest({ + isEnabled, + model, + alexRequest, + agentData, + serverOrigin, + }); + + res.json(response); + } catch (error: unknown) { + console.error(error); + return res.status(500).send({ error: (error as Error).message }); + } + }); + + router.post('/publish', async (req: express.Request, res: express.Response) => { + try { + const agentData = req._agentData; + const agentName = agentData.name; + const agentDomain = agentData.domain; + const accessToken = req.body.accessToken; + const vendorId = req.body.vendorId; + const scheme = agentDomain.includes(':') ? 'http' : 'https'; + const endpoint = `${scheme}://${agentDomain}/alexa`; + + await createAlexaSkill(agentName, accessToken, vendorId, endpoint); + + return res.json({ success: true, message: 'Agent published to Alexa successfully' }); + } catch (error: unknown) { + console.error('Error publishing to Alexa:', error); + return res.status(500).json({ success: false, error: (error as Error).message }); + } + }); + } +} diff --git a/src/roles/alexa/Alexa.service.ts b/src/roles/alexa/Alexa.service.ts new file mode 100644 index 0000000..6961700 --- /dev/null +++ b/src/roles/alexa/Alexa.service.ts @@ -0,0 +1,250 @@ +import axios from 'axios'; + +import { ConnectorService, Conversation } from '@smythos/sdk/core'; + +const SPEAKABLE_FORMAT_PROMPT = 'Return the response in speakable format'; +const ALEXA_BASE_URL = 'https://api.amazonalexa.com'; +const ALEXA_SETTINGS_KEY = 'alexa'; + +export async function handleAlexaRequest({ + isEnabled, + model, + alexRequest, + agentData, + serverOrigin, +}: { + isEnabled: boolean; + model: string; + alexRequest: any; + agentData: any; + serverOrigin: string; +}) { + if (!isEnabled) { + return buildAlexaResponse('Alexa is not enabled for this agent'); + } + if (alexRequest.type === 'LaunchRequest') { + return buildAlexaResponse('Hi I am smythos agent. What can I help you with?'); + } + const query = alexRequest.slots.searchQuery.heardAs; + const agentResponse = await processAlexaSearchQuery({ query, model, agentData, serverOrigin }); + const response = buildAlexaResponse(agentResponse); + return response; +} + +export function parseAlexaRequest(alexRequest: any) { + const type = alexRequest.request.type; + const intent = alexRequest.request.intent; + const slots = intent?.slots ? getSlotValues(intent.slots) : {}; + return { type, intent, slots }; +} + +export function getSlotValues(filledSlots) { + const slotValues = {}; + + Object.keys(filledSlots).forEach((item) => { + const name = filledSlots[item].name; + + if ( + filledSlots[item] && + filledSlots[item].resolutions && + filledSlots[item].resolutions.resolutionsPerAuthority[0] && + filledSlots[item].resolutions.resolutionsPerAuthority[0].status && + filledSlots[item].resolutions.resolutionsPerAuthority[0].status.code + ) { + switch (filledSlots[item].resolutions.resolutionsPerAuthority[0].status.code) { + case 'ER_SUCCESS_MATCH': + slotValues[name] = { + heardAs: filledSlots[item].value, + resolved: filledSlots[item].resolutions.resolutionsPerAuthority[0].values[0].value.name, + ERstatus: 'ER_SUCCESS_MATCH', + }; + break; + case 'ER_SUCCESS_NO_MATCH': + slotValues[name] = { + heardAs: filledSlots[item].value, + resolved: '', + ERstatus: 'ER_SUCCESS_NO_MATCH', + }; + break; + default: + break; + } + } else { + slotValues[name] = { + heardAs: filledSlots[item].value || '', // may be null + resolved: '', + ERstatus: '', + }; + } + }, this); + + return slotValues; +} + +export function buildAlexaResponse(outputSpeech: string, reprompt = '', shouldEndSession = false) { + return { + version: '1.0', + sessionAttributes: {}, + response: { + outputSpeech: { + type: 'PlainText', + text: outputSpeech, + }, + reprompt: { + outputSpeech: { + type: 'PlainText', + text: reprompt, + }, + }, + shouldEndSession: shouldEndSession, + }, + }; +} + +export async function createAlexaSkill(agentName: string, accessToken: string, vendorId: string, endpoint: string) { + try { + const response = await axios({ + method: 'post', + url: `${ALEXA_BASE_URL}/v1/skills`, + headers: { + Authorization: 'Bearer ' + accessToken, + 'Content-Type': 'application/json', + }, + data: { + vendorId: vendorId, + manifest: { + apis: { + custom: { + endpoint: { + sslCertificateType: 'Wildcard', + uri: endpoint, + }, + interfaces: [], + locales: {}, + }, + }, + manifestVersion: '1.0', + publishingInformation: { + category: 'ORGANIZERS_AND_ASSISTANTS', + locales: { + 'en-US': { + description: 'Smythos agent', + examplePhrases: ['Alexa open ' + agentName], + keywords: [agentName], + name: agentName, + summary: 'invoke ' + agentName, + }, + }, + }, + }, + }, + }); + const skillId = response.data.skillId; + await new Promise((resolve) => setTimeout(resolve, 2000)); + + await axios({ + method: 'put', + url: `${ALEXA_BASE_URL}/v1/skills/${skillId}/stages/development/interactionModel/locales/en-US`, + headers: { + Authorization: 'Bearer ' + accessToken, + 'Content-Type': 'application/json', + }, + data: { + interactionModel: { + languageModel: { + invocationName: agentName.toLowerCase(), + intents: [ + { + name: 'AMAZON.CancelIntent', + samples: [], + }, + { + name: 'AMAZON.HelpIntent', + samples: [], + }, + { + name: 'AMAZON.StopIntent', + samples: [], + }, + { + name: 'AMAZON.FallbackIntent', + samples: [], + }, + { + name: 'AMAZON.NavigateHomeIntent', + samples: [], + }, + { + name: 'SmythosQuery', + slots: [ + { + name: 'searchQuery', + type: 'AMAZON.SearchQuery', + }, + ], + samples: ['Hi {searchQuery}', 'Hello {searchQuery}'], + }, + ], + types: [], + }, + }, + }, + }); + return skillId; + } catch (error: any) { + console.error(error?.response?.data); + } +} + +export function processAlexaSearchQuery({ + query, + model, + agentData, + serverOrigin, +}: { + query: string; + model: string; + agentData: any; + serverOrigin: string; +}): Promise { + return new Promise((resolve, reject) => { + let result = ''; + + const agentDataConnector = ConnectorService.getAgentDataConnector(); + + agentDataConnector + .getOpenAPIJSON(agentData, serverOrigin, agentData.usingTestDomain ? '' : 'latest', true) + .then((spec) => { + const conversation = new Conversation(model, spec, { + agentId: agentData.id, + }); + conversation.on('error', (error) => { + console.error('Error in conversation:', error); + reject(new Error('An error occurred. Please try again later or select a different model.')); + }); + conversation.on('content', (content) => { + if (content?.indexOf('}{') >= 0) { + content = content.replace(/}{/g, '} {'); + } + result += content; + }); + conversation.on('end', () => { + console.log('streaming: [DONE]'); + resolve(result); + }); + conversation.streamPrompt(`${query} ${SPEAKABLE_FORMAT_PROMPT}`, { + 'X-AGENT-ID': agentData.id, + }); + }) + .catch((error) => { + reject(error); + }); + }); +} + +export function isAlexaEnabled(agentData: any, agentSettings: any) { + if (agentData.usingTestDomain) { + return true; + } + return agentSettings?.get(ALEXA_SETTINGS_KEY) === 'true'; +} From 37539ac488463c21ac94cdcedd5efcb3be75dc7e Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 00:21:11 +0600 Subject: [PATCH 5/9] move chat service to openai role directory --- .../chat.service.ts => roles/openai/Chat.service.ts} | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename src/{services/chat.service.ts => roles/openai/Chat.service.ts} (97%) diff --git a/src/services/chat.service.ts b/src/roles/openai/Chat.service.ts similarity index 97% rename from src/services/chat.service.ts rename to src/roles/openai/Chat.service.ts index 64120b5..5017ac5 100644 --- a/src/services/chat.service.ts +++ b/src/roles/openai/Chat.service.ts @@ -4,7 +4,7 @@ import { Readable } from 'stream'; import { faker } from '@faker-js/faker'; import { OpenAI } from 'openai'; -import { AccessCandidate, Conversation, ConnectorService, Logger } from '@smythos/sdk/core'; +import { AccessCandidate, ConnectorService, Conversation, Logger } from '@smythos/sdk/core'; import APIError from '@/APIError.class'; import { getAgentIdAndVersion } from '@/utils/agent.utils'; @@ -224,7 +224,9 @@ class OpenAIChatService { object: 'chat.completion', created: now, model: params.model, - choices: [{ index: 0, message: { role: 'assistant', content: result, refusal: null }, logprobs: null, finish_reason: 'stop' }], + choices: [ + { index: 0, message: { role: 'assistant', content: result, refusal: null }, logprobs: null, finish_reason: 'stop' }, + ], }; } } @@ -234,7 +236,7 @@ class OpenAIChatService { readable: Readable, completionId: string, now: number, - params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming + params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming, ) { const shouldEmitStatus = this.firstTime || Math.random() < 0.5; if (this.firstTime) { From 6acc57eb2603551cead430fd1de5329cb266aa82 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Wed, 19 Nov 2025 15:30:28 +0600 Subject: [PATCH 6/9] simplify parameters of resolve() method --- src/roles/Base.role.ts | 33 ++++++++++++++++--------------- src/roles/alexa/Alexa.role.ts | 11 ++++++----- src/roles/chatgpt/ChatGPT.role.ts | 2 +- src/roles/openai/OpenAI.role.ts | 9 +++++---- src/roles/postman/Postman.role.ts | 2 +- src/roles/swagger/Swagger.role.ts | 2 +- 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/roles/Base.role.ts b/src/roles/Base.role.ts index 70ab153..1d693dd 100644 --- a/src/roles/Base.role.ts +++ b/src/roles/Base.role.ts @@ -31,31 +31,32 @@ export class BaseRole { * @template Args - The argument object type for the resolver function * * @param resolvable - Either a static value of type T or a function that returns T - * @param options - Configuration object - * @param options.args - Argument object to pass to the resolver function (if resolvable is a function) - * @param options.defaultValue - Optional default value to use if resolvable is undefined or returns undefined + * @param args - Optional argument object to pass to the resolver function (required only if resolvable is a function) + * @param defaultValue - Optional default value to use if resolvable is undefined or returns undefined * @returns The resolved value of type T, or defaultValue, or undefined * * @example - * -- Resolve server origin without default (returns string | undefined) - * const origin = this.resolve(this.options.serverOrigin, { args: { req } }); + * -- Resolve static value with default + * const timeout = this.resolve(this.options.timeout, undefined, 5000); + * + * @example + * -- Resolve dynamic value without default (returns string | undefined) + * const origin = this.resolve(this.options.serverOrigin, req); * * @example * -- Resolve model with default fallback (always returns string) - * const model = this.resolve(this.options.model, { - * args: { baseModel, planInfo: agentData?.planInfo || {} }, - * defaultValue: baseModel - * }); + * const model = this.resolve( + * this.options.model, + * { baseModel, planInfo: agentData?.planInfo || {} }, + * baseModel + * ); */ - protected resolve( - resolvable: Resolvable | undefined, - options: { args: Args; defaultValue?: T }, - ): T | undefined { + protected resolve(resolvable: Resolvable | undefined, args?: Args, defaultValue?: T): T | undefined { if (resolvable === undefined) { - return options.defaultValue; + return defaultValue; } - const resolved = typeof resolvable === 'function' ? (resolvable as (args: Args) => T)(options.args) : resolvable; - return resolved !== undefined ? resolved : options.defaultValue; + const resolved = typeof resolvable === 'function' ? (resolvable as (args: Args) => T)(args!) : resolvable; + return resolved !== undefined ? resolved : defaultValue; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/roles/alexa/Alexa.role.ts b/src/roles/alexa/Alexa.role.ts index 8db79ce..55f30d8 100644 --- a/src/roles/alexa/Alexa.role.ts +++ b/src/roles/alexa/Alexa.role.ts @@ -32,7 +32,7 @@ export class OpenAPIRole extends BaseRole { await agentSettings?.embodiments?.ready(); // Resolve server origin: static string or dynamic from request - const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); + const serverOrigin = this.resolve(this.options.serverOrigin, req); const alexRequest = parseAlexaRequest(req.body); @@ -40,10 +40,11 @@ export class OpenAPIRole extends BaseRole { const baseModel = agentSettings?.get(DEFAULT_AGENT_MODEL_SETTINGS_KEY) || DEFAULT_AGENT_MODEL; // Apply model resolution strategy: static string, dynamic function, or default to base model - const model = this.resolve(this.options?.model, { - args: { baseModel, planInfo: agentData?.planInfo || {} }, - defaultValue: baseModel, - }); + const model = this.resolve( + this.options?.model, + { baseModel, planInfo: agentData?.planInfo || {} }, + baseModel, + ); const response = await handleAlexaRequest({ isEnabled, diff --git a/src/roles/chatgpt/ChatGPT.role.ts b/src/roles/chatgpt/ChatGPT.role.ts index 3b2c962..081b4e4 100644 --- a/src/roles/chatgpt/ChatGPT.role.ts +++ b/src/roles/chatgpt/ChatGPT.role.ts @@ -37,7 +37,7 @@ export class ChatGPTRole extends BaseRole { const agentData = req._agentData; // Resolve server origin from options (static value or dynamic function) - const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); + const serverOrigin = this.resolve(this.options.serverOrigin, req); // Fetch the base OpenAPI 3.0.1 specification from agent data const agentDataConnector = ConnectorService.getAgentDataConnector(); diff --git a/src/roles/openai/OpenAI.role.ts b/src/roles/openai/OpenAI.role.ts index 3cc7d4d..6a30da1 100644 --- a/src/roles/openai/OpenAI.role.ts +++ b/src/roles/openai/OpenAI.role.ts @@ -44,10 +44,11 @@ export class OpenAIRole extends BaseRole { const baseModel = agentSettings?.get(DEFAULT_AGENT_MODEL_SETTINGS_KEY) || DEFAULT_AGENT_MODEL; // Apply model resolution strategy: static string, dynamic function, or default to base model - const model = this.resolve(this.options?.model, { - args: { baseModel, planInfo: agentData?.planInfo || {} }, - defaultValue: baseModel, - }); + const model = this.resolve( + this.options?.model, + { baseModel, planInfo: agentData?.planInfo || {} }, + baseModel, + ); const authHeader = req.headers['authorization']; const apiKey = extractBearerToken(authHeader); diff --git a/src/roles/postman/Postman.role.ts b/src/roles/postman/Postman.role.ts index d63b344..cc93d58 100644 --- a/src/roles/postman/Postman.role.ts +++ b/src/roles/postman/Postman.role.ts @@ -43,7 +43,7 @@ export class PostmanRole extends BaseRole { router.get('/', middlewares, async (req: express.Request, res: express.Response) => { const agentData = req._agentData; try { - const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); + const serverOrigin = this.resolve(this.options.serverOrigin, req); const agentDataConnector = ConnectorService.getAgentDataConnector(); const openAPISpec = await agentDataConnector diff --git a/src/roles/swagger/Swagger.role.ts b/src/roles/swagger/Swagger.role.ts index e6e6d6c..3b0d0fc 100644 --- a/src/roles/swagger/Swagger.role.ts +++ b/src/roles/swagger/Swagger.role.ts @@ -35,7 +35,7 @@ export class SwaggerRole extends BaseRole { const isTestDomain = agentData.usingTestDomain; //const openApiDocument = await getOpenAPIJSON(agentData, domain, req._agentVersion, false); - const serverOrigin = this.resolve(this.options.serverOrigin, { args: req }); + const serverOrigin = this.resolve(this.options.serverOrigin, req); const agentDataConnector = ConnectorService.getAgentDataConnector(); const openApiDocument = await agentDataConnector.getOpenAPIJSON(agentData, serverOrigin, agentData.version, false); From 9efce0297fbf69b1cb423160d7b241e9b7beb435 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Sun, 23 Nov 2025 15:07:57 +0600 Subject: [PATCH 7/9] fix typo --- src/index.ts | 8 +++++++- src/index.ts.bak | 5 +++-- src/roles/alexa/Alexa.role.ts | 10 +++------- src/roles/openapi/OpenAPI.role.ts | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 91e40bb..2f8173f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,9 @@ export { version } from '../package.json'; + + + export * from './APIError.class'; export * from './constants'; export * from './middlewares/AgentLoader.mw'; @@ -9,11 +12,14 @@ export * from './middlewares/CORS.mw'; export * from './middlewares/RemoveHeadersFactory.mw'; export * from './middlewares/UploadHandlerFactory.mw'; export * from './roles/Base.role'; -export * from './roles/openai/Chat.service'; +export * from './types/resolvers.types'; export * from './utils/agent.utils'; export * from './roles/agent/Agent.role'; export * from './roles/agent/AgentRequestHandler'; +export * from './roles/alexa/Alexa.role'; +export * from './roles/alexa/Alexa.service'; export * from './roles/chatgpt/ChatGPT.role'; +export * from './roles/openai/Chat.service'; export * from './roles/openai/chat.validation'; export * from './roles/openai/OpenAI.role'; export * from './roles/openai/utils'; diff --git a/src/index.ts.bak b/src/index.ts.bak index fb80747..cd6cf61 100644 --- a/src/index.ts.bak +++ b/src/index.ts.bak @@ -12,15 +12,16 @@ export * from './middlewares/CORS.mw'; export * from './middlewares/RemoveHeadersFactory.mw'; export * from './middlewares/UploadHandlerFactory.mw'; export * from './roles/Base.role'; -export * from './services/chat.service'; +export * from './types/resolvers.types'; export * from './utils/agent.utils'; export * from './roles/agent/Agent.role'; export * from './roles/agent/AgentRequestHandler'; +export * from './roles/alexa/Alexa.service'; export * from './roles/chatgpt/ChatGPT.role'; +export * from './roles/openai/Chat.service'; export * from './roles/openai/chat.validation'; export * from './roles/openai/OpenAI.role'; export * from './roles/openai/utils'; -export * from './roles/openapi/OpenAPI.role'; export * from './roles/postman/Postman.role'; export * from './roles/swagger/Swagger.role'; export * from './roles/openai/middlewares/AgentDataAdapter.mw'; diff --git a/src/roles/alexa/Alexa.role.ts b/src/roles/alexa/Alexa.role.ts index 55f30d8..4b5c014 100644 --- a/src/roles/alexa/Alexa.role.ts +++ b/src/roles/alexa/Alexa.role.ts @@ -7,9 +7,9 @@ import type { ModelResolver, ServerOriginResolver } from '@/types/resolvers.type import { createAlexaSkill, handleAlexaRequest, isAlexaEnabled, parseAlexaRequest } from './Alexa.service'; -export class OpenAPIRole extends BaseRole { +export class AlexaRole extends BaseRole { /** - * Creates a new OpenAPIRole instance. + * Creates a new AlexaRole instance. * @param middlewares - The custom middlewares to apply to the role on top of the default middlewares. * @param options - The options for the role * @param options.serverOrigin - Server origin URL: string for static, or function to resolve dynamically from request @@ -40,11 +40,7 @@ export class OpenAPIRole extends BaseRole { const baseModel = agentSettings?.get(DEFAULT_AGENT_MODEL_SETTINGS_KEY) || DEFAULT_AGENT_MODEL; // Apply model resolution strategy: static string, dynamic function, or default to base model - const model = this.resolve( - this.options?.model, - { baseModel, planInfo: agentData?.planInfo || {} }, - baseModel, - ); + const model = this.resolve(this.options?.model, { baseModel, planInfo: agentData?.planInfo || {} }, baseModel); const response = await handleAlexaRequest({ isEnabled, diff --git a/src/roles/openapi/OpenAPI.role.ts b/src/roles/openapi/OpenAPI.role.ts index 5a7c23b..6292f90 100644 --- a/src/roles/openapi/OpenAPI.role.ts +++ b/src/roles/openapi/OpenAPI.role.ts @@ -11,7 +11,7 @@ export class OpenAPIRole extends BaseRole { * @param middlewares - The custom middlewares to apply to the role on top of the default middlewares. * @param options - The options for the role. Defaults to an empty object. */ - constructor(middlewares: express.RequestHandler[] = [], options: Record string)> = {}) { + constructor(middlewares: express.RequestHandler[] = [], options: Record = {}) { super(middlewares, options); } From e32ab62079a8410840d477f91422de9983a7a4f7 Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Sun, 23 Nov 2025 15:19:35 +0600 Subject: [PATCH 8/9] add missing middlewares to /publish route --- src/roles/alexa/Alexa.role.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roles/alexa/Alexa.role.ts b/src/roles/alexa/Alexa.role.ts index 4b5c014..122ac1f 100644 --- a/src/roles/alexa/Alexa.role.ts +++ b/src/roles/alexa/Alexa.role.ts @@ -57,7 +57,7 @@ export class AlexaRole extends BaseRole { } }); - router.post('/publish', async (req: express.Request, res: express.Response) => { + router.post('/publish', middlewares, async (req: express.Request, res: express.Response) => { try { const agentData = req._agentData; const agentName = agentData.name; From 1ee5654d4594569a170e8a772a46524ccb247dcd Mon Sep 17 00:00:00 2001 From: Forhad Hosain Date: Tue, 25 Nov 2025 21:58:29 +0600 Subject: [PATCH 9/9] add index.ts.bak --- src/index.ts.bak | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts.bak b/src/index.ts.bak index cd6cf61..2f8173f 100644 --- a/src/index.ts.bak +++ b/src/index.ts.bak @@ -16,12 +16,14 @@ export * from './types/resolvers.types'; export * from './utils/agent.utils'; export * from './roles/agent/Agent.role'; export * from './roles/agent/AgentRequestHandler'; +export * from './roles/alexa/Alexa.role'; export * from './roles/alexa/Alexa.service'; export * from './roles/chatgpt/ChatGPT.role'; export * from './roles/openai/Chat.service'; export * from './roles/openai/chat.validation'; export * from './roles/openai/OpenAI.role'; export * from './roles/openai/utils'; +export * from './roles/openapi/OpenAPI.role'; export * from './roles/postman/Postman.role'; export * from './roles/swagger/Swagger.role'; export * from './roles/openai/middlewares/AgentDataAdapter.mw';