Skip to content

Commit

Permalink
Merge pull request #251 from giselles-ai/implement-agent-time-record-…
Browse files Browse the repository at this point in the history
…and-reporting-to-playground-v2

Implement agent time recording and reporting to playground v2
  • Loading branch information
satococoa authored Dec 19, 2024
2 parents b713b82 + f20ad99 commit 5e1f183
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 10 deletions.
24 changes: 21 additions & 3 deletions app/(playground)/p/[agentId]/contexts/execution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import type {
FailedStepExecution,
Flow,
FlowId,
GeneratedArtifact,
JobExecution,
Node,
NodeId,
Expand Down Expand Up @@ -377,6 +376,11 @@ interface ExecutionContextType {
executionId: ExecutionId,
forceRetryStepId?: StepId,
) => Promise<void>;
recordAgentUsageAction: (
startedAt: number,
endedAt: number,
totalDurationMs: number,
) => Promise<void>;
}

const ExecutionContext = createContext<ExecutionContextType | undefined>(
Expand Down Expand Up @@ -407,6 +411,11 @@ interface ExecutionProviderProps {
executionId: ExecutionId,
nodeId: NodeId,
) => Promise<StreamableValue<TextArtifactObject, unknown>>;
recordAgentUsageAction: (
startedAt: number,
endedAt: number,
totalDurationMs: number,
) => Promise<void>;
}

export function ExecutionProvider({
Expand All @@ -415,6 +424,7 @@ export function ExecutionProvider({
putExecutionAction,
retryStepAction,
executeNodeAction,
recordAgentUsageAction,
}: ExecutionProviderProps) {
const { dispatch, flush, graph } = useGraph();
const { setTab } = usePropertiesPanel();
Expand Down Expand Up @@ -531,20 +541,27 @@ export function ExecutionProvider({
});

const { blobUrl } = await putExecutionAction(executionSnapshot);
const runEndedAt = Date.now();
dispatch({
type: "addExecutionIndex",
input: {
executionIndex: {
executionId: currentExecution.id,
blobUrl,
completedAt: Date.now(),
completedAt: runEndedAt,
},
},
});

await recordAgentUsageAction(
currentExecution.runStartedAt,
runEndedAt,
currentExecution.durationMs,
);

return currentExecution;
},
[dispatch, putExecutionAction, addToast],
[dispatch, putExecutionAction, addToast, recordAgentUsageAction],
);

const executeFlow = useCallback(
Expand Down Expand Up @@ -737,6 +754,7 @@ export function ExecutionProvider({
executeFlow,
retryFlowExecution,
executeNode,
recordAgentUsageAction,
}}
>
{children}
Expand Down
13 changes: 11 additions & 2 deletions app/(playground)/p/[agentId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
withCountMeasurement,
} from "@/lib/opentelemetry";
import { getUser } from "@/lib/supabase";
import { recordAgentUsage } from "@/services/agents/activities";
import { del, list, put } from "@vercel/blob";
import { ReactFlowProvider } from "@xyflow/react";
import { eq } from "drizzle-orm";
Expand All @@ -30,8 +31,6 @@ import { buildGraphExecutionPath, buildGraphFolderPath } from "./lib/utils";
import type {
AgentId,
Artifact,
ArtifactId,
Execution,
ExecutionId,
ExecutionSnapshot,
FlowId,
Expand Down Expand Up @@ -197,6 +196,15 @@ export default async function Page({
return await executeNode(agentId, executionId, nodeId);
}

async function recordAgentUsageAction(
startedAt: number,
endedAt: number,
totalDurationMs: number,
) {
"use server";
return await recordAgentUsage(agentId, startedAt, endedAt, totalDurationMs);
}

return (
<DeveloperModeProvider developerMode={developerMode}>
<GraphContextProvider
Expand All @@ -218,6 +226,7 @@ export default async function Page({
executeStepAction={executeStepAction}
putExecutionAction={putExecutionAction}
retryStepAction={retryStepAction}
recordAgentUsageAction={recordAgentUsageAction}
executeNodeAction={executeNodeAction}
>
<Playground />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { AgentId } from "../types";

/**
* @deprecated
*/
export class AgentActivity {
private actions: AgentActivityAction[] = [];
public agentId: AgentId;
Expand Down
4 changes: 3 additions & 1 deletion services/agents/activities/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { calculateAgentTimeUsageMs } from "./agent-time-usage";
export { AGENT_TIME_CHARGE_LIMIT_MINUTES } from "./constants";
export { AgentActivity } from "./deprecated-agent-activity";
export { hasEnoughAgentTimeCharge } from "./has-enough-agent-time-charge";
export { AgentActivity } from "./types";
export { recordAgentUsage } from "./record-agent-usage";
export { saveAgentActivity } from "./save-agent-activity";
export { getMonthlyBillingCycle } from "./utils";
21 changes: 21 additions & 0 deletions services/agents/activities/record-agent-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { toUTCDate } from "@/lib/date";
import { reportAgentTimeUsage } from "@/services/usage-based-billing/report-agent-time-usage";
import type { AgentId } from "../types";
import { saveAgentActivity } from "./save-agent-activity";

export async function recordAgentUsage(
agentId: AgentId,
startedAt: number,
endedAt: number,
totalDurationMs: number,
) {
const startedAtDateUTC = toUTCDate(new Date(startedAt));
const endedAtDateUTC = toUTCDate(new Date(endedAt));
await saveAgentActivity(
agentId,
startedAtDateUTC,
endedAtDateUTC,
totalDurationMs,
);
await reportAgentTimeUsage(endedAtDateUTC);
}
26 changes: 26 additions & 0 deletions services/agents/activities/save-agent-activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { agentActivities, agents, db } from "@/drizzle";
import type { AgentId } from "@/services/agents";
import { eq } from "drizzle-orm";

export async function saveAgentActivity(
agentId: AgentId,
startedAt: Date,
endedAt: Date,
totalDurationMs: number,
) {
const records = await db
.select({ agentDbId: agents.dbId })
.from(agents)
.where(eq(agents.id, agentId));
if (records.length === 0) {
throw new Error(`Agent with id ${agentId} not found`);
}
const agentDbId = records[0].agentDbId;

await db.insert(agentActivities).values({
agentDbId,
startedAt: startedAt,
endedAt: endedAt,
totalDurationMs: totalDurationMs.toString(),
});
}
23 changes: 19 additions & 4 deletions services/external/stripe/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { Stripe } from "stripe";

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2024-11-20.acacia",
});
let stripeInstance: Stripe | null = null;

const handler: ProxyHandler<Stripe> = {
get: (_target, prop: keyof Stripe | symbol) => {
if (!stripeInstance) {
const key = process.env.STRIPE_SECRET_KEY;
if (!key) {
throw new Error("STRIPE_SECRET_KEY is not configured");
}
stripeInstance = new Stripe(key, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: "2024-11-20.acacia",
});
}
return stripeInstance[prop as keyof Stripe];
},
};

export const stripe: Stripe = new Proxy(new Stripe("dummy"), handler);
22 changes: 22 additions & 0 deletions services/usage-based-billing/report-agent-time-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { db } from "@/drizzle";
import { stripe } from "@/services/external/stripe";
import { fetchCurrentTeam } from "@/services/teams";
import { processUnreportedActivities } from "@/services/usage-based-billing";
import { AgentTimeUsageDAO } from "@/services/usage-based-billing/agent-time-usage-dao";

export async function reportAgentTimeUsage(targetDate: Date) {
const currentTeam = await fetchCurrentTeam();
if (currentTeam.activeSubscriptionId == null) {
return;
}
return processUnreportedActivities(
{
teamDbId: currentTeam.dbId,
targetDate: targetDate,
},
{
dao: new AgentTimeUsageDAO(db),
stripe: stripe,
},
);
}

0 comments on commit 5e1f183

Please sign in to comment.