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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.

<!-- PLUGINS:START - Do not remove. Auto-generated by discover-plugins -->
- **AI Gateway**: Generate Text, Generate Image
- **Axiom**: Query Logs, Ingest Events, Create Annotation, List Datasets
- **Blob**: Put Blob, List Blobs
- **Firecrawl**: Scrape URL, Search Web
- **GitHub**: Create Issue, List Issues, Get Issue, Update Issue
Expand Down
4 changes: 4 additions & 0 deletions plugins/axiom/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type AxiomCredentials = {
AXIOM_TOKEN?: string;
AXIOM_ORG_ID?: string;
};
14 changes: 14 additions & 0 deletions plugins/axiom/icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function AxiomIcon({ className }: { className?: string }) {
return (
<svg
aria-label="Axiom logo"
className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Axiom</title>
<path d="M12 0L1.608 6v12L12 24l10.392-6V6L12 0zm0 2.304l8.16 4.728v9.456L12 21.696l-8.16-4.728V7.032L12 2.304zm0 3.456L6.48 9v6l5.52 3.24 5.52-3.24V9L12 5.76zm0 2.304l3.36 1.944v3.888L12 15.84l-3.36-1.944v-3.888L12 8.064z" />
</svg>
);
}
201 changes: 201 additions & 0 deletions plugins/axiom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import type { IntegrationPlugin } from "../registry";
import { registerIntegration } from "../registry";
import { AxiomIcon } from "./icon";

const axiomPlugin: IntegrationPlugin = {
type: "axiom",
label: "Axiom",
description: "Query logs, ingest events, and create annotations in Axiom",

icon: AxiomIcon,

formFields: [
{
id: "token",
label: "API Token",
type: "password",
placeholder: "xaat-...",
configKey: "token",
envVar: "AXIOM_TOKEN",
helpText: "Get your API token from ",
helpLink: {
text: "app.axiom.co/settings/api-tokens",
url: "https://app.axiom.co/settings/api-tokens",
},
},
{
id: "orgId",
label: "Organization ID",
type: "text",
placeholder: "my-org-123",
configKey: "orgId",
envVar: "AXIOM_ORG_ID",
helpText: "Required for personal tokens. Find it in your org settings.",
},
],

testConfig: {
getTestFunction: async () => {
const { testAxiom } = await import("./test");
return testAxiom;
},
},

actions: [
{
slug: "query-logs",
label: "Query Logs",
description: "Run an APL query against an Axiom dataset",
category: "Axiom",
stepFunction: "queryLogsStep",
stepImportPath: "query-logs",
outputFields: [
{ field: "matches", description: "Array of matching log entries" },
{ field: "count", description: "Number of results returned" },
{ field: "status.elapsedTime", description: "Query execution time" },
],
configFields: [
{
key: "dataset",
label: "Dataset",
type: "template-input",
placeholder: "my-dataset",
example: "vercel",
required: true,
},
{
key: "apl",
label: "APL Query",
type: "template-textarea",
placeholder:
"['my-dataset'] | where level == 'error' | limit 100",
example: "['vercel'] | where level == 'error' | limit 10",
rows: 4,
required: true,
},
{
key: "startTime",
label: "Start Time",
type: "template-input",
placeholder: "2024-01-01T00:00:00Z or -1h",
example: "-1h",
},
{
key: "endTime",
label: "End Time",
type: "template-input",
placeholder: "2024-01-01T23:59:59Z or now",
example: "now",
},
],
},
{
slug: "ingest-events",
label: "Ingest Events",
description: "Send log events to an Axiom dataset",
category: "Axiom",
stepFunction: "ingestEventsStep",
stepImportPath: "ingest-events",
outputFields: [
{ field: "ingested", description: "Number of events ingested" },
{ field: "processedBytes", description: "Bytes processed" },
],
configFields: [
{
key: "dataset",
label: "Dataset",
type: "template-input",
placeholder: "my-dataset",
example: "workflow-logs",
required: true,
},
{
key: "events",
label: "Events (JSON)",
type: "template-textarea",
placeholder:
'[{"level": "info", "message": "Hello"}] or {{NodeName.data}}',
example: '[{"level": "info", "message": "Workflow executed"}]',
rows: 6,
required: true,
},
],
},
{
slug: "create-annotation",
label: "Create Annotation",
description:
"Create an annotation to mark deployments, incidents, or events",
category: "Axiom",
stepFunction: "createAnnotationStep",
stepImportPath: "create-annotation",
outputFields: [
{ field: "id", description: "Annotation ID" },
{ field: "time", description: "Annotation timestamp" },
],
configFields: [
{
key: "datasets",
label: "Datasets",
type: "template-input",
placeholder: "dataset1,dataset2",
example: "vercel,api-logs",
required: true,
},
{
key: "type",
label: "Type",
type: "select",
options: [
{ value: "deploy", label: "Deployment" },
{ value: "incident", label: "Incident" },
{ value: "config-change", label: "Config Change" },
{ value: "alert", label: "Alert" },
{ value: "other", label: "Other" },
],
defaultValue: "deploy",
},
{
key: "title",
label: "Title",
type: "template-input",
placeholder: "Production deployment v1.2.3",
example: "Deployed v1.2.3",
required: true,
},
{
key: "description",
label: "Description",
type: "template-textarea",
placeholder: "Additional details about this annotation",
example: "Deployed new feature: user authentication",
rows: 3,
},
{
key: "url",
label: "URL",
type: "template-input",
placeholder: "https://github.com/org/repo/releases/tag/v1.2.3",
example: "https://github.com/myorg/myrepo/releases",
},
],
},
{
slug: "list-datasets",
label: "List Datasets",
description: "Get all available datasets in your Axiom organization",
category: "Axiom",
stepFunction: "listDatasetsStep",
stepImportPath: "list-datasets",
outputFields: [
{ field: "datasets", description: "Array of dataset objects" },
{ field: "count", description: "Number of datasets" },
],
configFields: [],
},
],
};

registerIntegration(axiomPlugin);

export default axiomPlugin;
146 changes: 146 additions & 0 deletions plugins/axiom/steps/create-annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import "server-only";

import { fetchCredentials } from "@/lib/credential-fetcher";
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
import { getErrorMessage } from "@/lib/utils";
import type { AxiomCredentials } from "../credentials";

const AXIOM_API_URL = "https://api.axiom.co";

type AxiomAnnotationResponse = {
id: string;
datasets: string[];
type: string;
title: string;
description?: string;
url?: string;
time: string;
endTime?: string;
};

type CreateAnnotationResult =
| {
success: true;
id: string;
time: string;
datasets: string[];
}
| { success: false; error: string };

export type CreateAnnotationCoreInput = {
datasets: string;
type: string;
title: string;
description?: string;
url?: string;
};

export type CreateAnnotationInput = StepInput &
CreateAnnotationCoreInput & {
integrationId?: string;
};

async function stepHandler(
input: CreateAnnotationCoreInput,
credentials: AxiomCredentials
): Promise<CreateAnnotationResult> {
const token = credentials.AXIOM_TOKEN;

if (!token) {
return {
success: false,
error:
"AXIOM_TOKEN is not configured. Please add it in Project Integrations.",
};
}

try {
// Parse comma-separated datasets
const datasets = input.datasets
.split(",")
.map((d) => d.trim())
.filter(Boolean);

if (datasets.length === 0) {
return {
success: false,
error: "At least one dataset is required",
};
}

const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};

if (credentials.AXIOM_ORG_ID) {
headers["X-Axiom-Org-Id"] = credentials.AXIOM_ORG_ID;
}

const body: Record<string, unknown> = {
datasets,
type: input.type || "deploy",
title: input.title,
time: new Date().toISOString(),
};

if (input.description) {
body.description = input.description;
}

if (input.url) {
body.url = input.url;
}

const response = await fetch(`${AXIOM_API_URL}/v2/annotations`, {
method: "POST",
headers,
body: JSON.stringify(body),
});

if (!response.ok) {
const errorText = await response.text();
let errorMessage = `HTTP ${response.status}`;
try {
const errorJson = JSON.parse(errorText) as { message?: string };
errorMessage = errorJson.message || errorMessage;
} catch {
if (errorText) {
errorMessage = errorText;
}
}
return {
success: false,
error: `Failed to create annotation: ${errorMessage}`,
};
}

const result = (await response.json()) as AxiomAnnotationResponse;

return {
success: true,
id: result.id,
time: result.time,
datasets: result.datasets,
};
} catch (error) {
return {
success: false,
error: `Failed to create annotation: ${getErrorMessage(error)}`,
};
}
}

export async function createAnnotationStep(
input: CreateAnnotationInput
): Promise<CreateAnnotationResult> {
"use step";

const credentials = input.integrationId
? await fetchCredentials(input.integrationId)
: {};

return withStepLogging(input, () => stepHandler(input, credentials));
}

export const _integrationType = "axiom";
Loading