-
-
Notifications
You must be signed in to change notification settings - Fork 24.7k
Add Asqav Sign Action tool node #6614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { INodeParams, INodeCredential } from '../src/Interface' | ||
|
|
||
| class AsqavApi implements INodeCredential { | ||
| label: string | ||
| name: string | ||
| version: number | ||
| description: string | ||
| inputs: INodeParams[] | ||
|
|
||
| constructor() { | ||
| this.label = 'Asqav API' | ||
| this.name = 'asqavApi' | ||
| this.version = 1.0 | ||
| this.description = | ||
| 'API key for Asqav. Issued in the Asqav dashboard. Used to sign agent actions and obtain cryptographic compliance receipts.' | ||
| this.inputs = [ | ||
| { | ||
| label: 'Asqav API Key', | ||
| name: 'asqavApiKey', | ||
| type: 'password' | ||
| } | ||
| ] | ||
| } | ||
| } | ||
|
|
||
| module.exports = { credClass: AsqavApi } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' | ||
| import { getCredentialData, getCredentialParam, parseJsonBody } from '../../../src/utils' | ||
| import { checkDenyList } from '../../../src/httpSecurity' | ||
|
|
||
| const DEFAULT_BASE_URL = 'https://api.asqav.com/api/v1' | ||
|
|
||
| class AsqavSignAction_Tools implements INode { | ||
| label: string | ||
| name: string | ||
| version: number | ||
| type: string | ||
| icon: string | ||
| category: string | ||
| description: string | ||
| baseClasses: string[] | ||
| credential: INodeParams | ||
| inputs: INodeParams[] | ||
|
|
||
| constructor() { | ||
| this.label = 'Asqav Sign Action' | ||
| this.name = 'asqavSignAction' | ||
| this.version = 1.0 | ||
| this.type = 'AsqavSignAction' | ||
| this.icon = 'asqav.svg' | ||
| this.category = 'Tools' | ||
| this.description = | ||
| 'Sign an agent action with Asqav and return the cryptographic compliance receipt (signature id, verification URL, timestamp)' | ||
| this.baseClasses = [this.type] | ||
| this.credential = { | ||
| label: 'Connect Credential', | ||
| name: 'credential', | ||
| type: 'credential', | ||
| credentialNames: ['asqavApi'] | ||
| } | ||
| this.inputs = [ | ||
| { | ||
| label: 'Action Type', | ||
| name: 'actionType', | ||
| type: 'string', | ||
| placeholder: 'api:call', | ||
| description: 'The type of action being signed, e.g. "api:call" or "tool:invoke"' | ||
| }, | ||
| { | ||
| label: 'Context', | ||
| name: 'context', | ||
| type: 'json', | ||
| optional: true, | ||
| description: 'Optional JSON metadata describing the action. Only the values you pass are hashed into the receipt.' | ||
| }, | ||
| { | ||
| label: 'Agent ID', | ||
| name: 'agentId', | ||
| type: 'string', | ||
| optional: true, | ||
| description: | ||
| 'Optional existing Asqav agent id to sign with. When omitted, a fresh ephemeral agent is created for the run.' | ||
| }, | ||
| { | ||
| label: 'Base URL', | ||
| name: 'baseUrl', | ||
| type: 'string', | ||
| optional: true, | ||
| placeholder: 'https://api.asqav.com/api/v1', | ||
| description: 'Asqav API base URL. Defaults to the production endpoint. Override for a self-hosted Asqav instance.' | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| async run(nodeData: INodeData, _input: string, options?: ICommonObject): Promise<string | ICommonObject> { | ||
| const credentialData = await getCredentialData(nodeData.credential ?? '', options ?? {}) | ||
| const apiKey = getCredentialParam('asqavApiKey', credentialData, nodeData) | ||
| if (!apiKey) { | ||
| throw new Error('Asqav Sign Action: API Key is required. Please configure the Asqav API credential.') | ||
| } | ||
|
|
||
| const actionType = (nodeData.inputs?.actionType as string) ?? '' | ||
| if (!actionType) { | ||
| throw new Error('Asqav Sign Action: "Action Type" is required.') | ||
| } | ||
|
|
||
| let context: Record<string, unknown> = {} | ||
| const rawContext = nodeData.inputs?.context | ||
| if (rawContext) { | ||
| if (typeof rawContext === 'string') { | ||
| const trimmed = rawContext.trim() | ||
| if (trimmed.length) { | ||
| try { | ||
| context = parseJsonBody(trimmed) | ||
| } catch { | ||
| throw new Error('Asqav Sign Action: "Context" must be valid JSON.') | ||
| } | ||
|
Comment on lines
+87
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use try {
context = parseJsonBody(trimmed)
} catch {
throw new Error('Asqav Sign Action: "Context" must be valid JSON.')
} |
||
| } | ||
| } else if (typeof rawContext === 'object') { | ||
| context = rawContext as Record<string, unknown> | ||
| } | ||
| } | ||
|
|
||
| const baseUrl = ((nodeData.inputs?.baseUrl as string) || DEFAULT_BASE_URL).replace(/\/+$/, '') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since const baseUrl = ((nodeData.inputs?.baseUrl as string) || DEFAULT_BASE_URL).replace(/\n+$/, '')
await checkDenyList(baseUrl) |
||
| await checkDenyList(baseUrl) | ||
| const headers = { | ||
| 'X-API-Key': apiKey, | ||
| 'Content-Type': 'application/json' | ||
| } | ||
|
|
||
| let agentId = ((nodeData.inputs?.agentId as string) ?? '').trim() | ||
| if (!agentId) { | ||
| const agentResponse = await fetch(`${baseUrl}/agents/create`, { | ||
| method: 'POST', | ||
| headers, | ||
| body: JSON.stringify({ | ||
| name: 'flowise', | ||
| algorithm: 'ml-dsa-65', | ||
| capabilities: [] | ||
| }) | ||
| }) | ||
| if (!agentResponse.ok) { | ||
| const detail = await agentResponse.text().catch(() => '') | ||
| throw new Error( | ||
| `Asqav Sign Action: agent create failed (${agentResponse.status} ${agentResponse.statusText})${detail ? `: ${detail}` : ''}` | ||
| ) | ||
| } | ||
| const agent = (await agentResponse.json()) as { agent_id: string } | ||
| agentId = agent.agent_id | ||
| } | ||
|
|
||
| const signResponse = await fetch(`${baseUrl}/agents/${agentId}/sign`, { | ||
| method: 'POST', | ||
| headers, | ||
| body: JSON.stringify({ | ||
| action_type: actionType, | ||
| context, | ||
| session_id: null, | ||
| compliance_mode: true | ||
| }) | ||
| }) | ||
| if (!signResponse.ok) { | ||
| const detail = await signResponse.text().catch(() => '') | ||
| throw new Error( | ||
| `Asqav Sign Action: sign failed (${signResponse.status} ${signResponse.statusText})${detail ? `: ${detail}` : ''}` | ||
| ) | ||
| } | ||
| const receipt = (await signResponse.json()) as { | ||
| signature_id: string | ||
| action_id: string | ||
| verification_url: string | ||
| timestamp: string | ||
| algorithm: string | ||
| [key: string]: unknown | ||
| } | ||
|
|
||
| return { | ||
| signatureId: receipt.signature_id, | ||
| actionId: receipt.action_id, | ||
| verificationUrl: receipt.verification_url, | ||
| timestamp: receipt.timestamp, | ||
| algorithm: receipt.algorithm, | ||
| receipt | ||
| } | ||
| } | ||
| } | ||
|
|
||
| module.exports = { nodeClass: AsqavSignAction_Tools } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate that the
apiKeyis present before proceeding. If it is missing, throw an error early to provide a clear and helpful error message to the user.References