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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/wwPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default {
xanoManager: null,
xanoClient: null,
channels: {},
fullSpec: [],
/*=============================================m_ÔÔ_m=============================================\
Plugin API
\================================================================================================*/
Expand Down Expand Up @@ -53,10 +54,16 @@ export default {
Editor API
\================================================================================================*/
/* wwEditor:start */
_getCopilotContext() {
return {
apiDoc: formatSpec(this.fullSpec),
};
},
async initManager(settings) {
this.xanoManager = this.createManager(settings);
try {
await this.xanoManager.init();
this.fullSpec = await this.xanoManager.fetchFullSpec();
} catch (error) {
wwLib.wwNotification.open({
text: 'Failed to init Xano, please ensure your API key has the permission required.',
Expand Down Expand Up @@ -275,3 +282,82 @@ function buildXanoHeaders(
.reduce((curr, next) => ({ ...curr, [next.key]: next.value }), {}),
};
}

function formatSpec(fullSpec) {
return fullSpec.map(spec => {
const result = {
apiGroupName: spec.info.title,
apiGroupUrl: spec.servers[0].url,
endpoints: {},
};

// Process all paths
for (const [path, methods] of Object.entries(spec.paths)) {
for (const [method, details] of Object.entries(methods)) {
const endpointKey = `${method}${path.replace(/\//g, '_')}`;

// Create endpoint info
const endpoint = {
apiGroupUrl: spec.servers[0].url,
path,
method,
requiresAuth: details.security?.length > 0,
summary: details.summary,
};

// Add path parameters if any
if (details.parameters?.length) {
endpoint.pathParams = details.parameters
.filter(p => p.in === 'path')
.map(p => ({
name: p.name,
type: p.schema.type,
required: p.required,
}));
}

// Add query parameters if any
if (details.parameters?.length) {
endpoint.queryParams = details.parameters
.filter(p => p.in === 'query')
.map(p => ({
name: p.name,
type: p.schema.type,
required: p.required,
}));
}

// Add request body if exists
const requestBody = details.requestBody?.content['application/json']?.schema;
if (requestBody) {
endpoint.requestSchema = {
type: requestBody.type,
properties: Object.entries(requestBody.properties).map(([key, value]) => ({
name: key,
type: value.type,
description: value.description,
enum: value.enum,
required: requestBody.required?.includes(key),
})),
};
}

// Add response schema if exists
const responseSchema = details.responses['200']?.content['application/json']?.schema;
if (responseSchema) {
endpoint.responseSchema = {
type: responseSchema.type,
properties:
responseSchema.type === 'array'
? responseSchema.items.properties
: responseSchema.properties,
};
}

result.endpoints[endpointKey] = endpoint;
}
}

return result;
});
}
62 changes: 62 additions & 0 deletions ww-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,68 @@ export default {
isAsync: true,
/* wwEditor:start */
edit: () => import('./src/components/Request.vue'),
copilot: {
description:
'Make a request to a Xano API endpoint. Can handle both regular REST requests and streaming responses. Automatically includes Xano authentication token if available.',
returns:
'For regular requests: Axios response object containing { data, status, headers, config }. For streaming requests: Array of accumulated stream data accessed through the specified streamVariableId.',
schema: {
apiGroupUrl: {
type: 'string',
description:
'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef").',
bindable: false,
},
endpoint: {
type: 'object',
description:
'The endpoint configuration object with required properties:\n- method: HTTP method (get, post, put, patch, delete)\n- path: Endpoint path with optional parameter placeholders (e.g., "/users/{userId}")',
bindable: false,
},
headers: {
type: 'Array<{key: string, value: string}',
description:
'Custom headers as key-value pairs, e.g., [{"Content-Type": "application/json"}]. Automatically includes Xano authentication token if available. key and value are bindable individually.',
bindable: true,
},
parameters: {
type: 'object',
description:
'URL parameters object serving two purposes: 1) Replace path placeholders (e.g., {userId} in path), 2) Add query parameters to URL. Example: {"userId": "123", "filter": "active"}. The values are bindable, but not the whole object.',
bindable: false,
},
body: {
type: 'object',
description:
'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The key values are bindable, but not the whole object. The object cannot be bind, you have to bind individual sub keys. eg. {email: {__wwType: "...", code: "..."}, password: {__wwType: "...", code: "..."}}',
bindable: false,
},
dataType: {
type: 'string',
description:
'Content type for the request. Set to "text/event-stream" for SSE streaming. Default is "application/json"',
bindable: true,
},
withCredentials: {
type: 'boolean',
description:
'Include credentials (cookies) with the request. Falls back to plugin settings if not specified.',
bindable: true,
},
useStreaming: {
type: 'boolean',
description:
'Enable Server-Sent Events (SSE) streaming mode. When true, responses will be accumulated in the specified streamVariableId.',
bindable: true,
},
streamVariableId: {
type: 'string',
description:
'Required when useStreaming is true. The ID of the variable where streaming responses will be accumulated as an array.',
bindable: true,
},
},
},
/* wwEditor:end */
},
{
Expand Down