Skip to content
Merged
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
9 changes: 2 additions & 7 deletions .github/workflows/setup-github-projects_Version3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,12 @@ jobs:
exit 1
fi

- name: Authenticate GH CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh auth login --with-token <<< "${GH_TOKEN}"

- name: Create Project with description
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PROJECT_NAME="Auto Project"
PROJECT_DESC="Automated project for task management with weekly iterations"
PROJECT_NAME="Agent Builder Application"
PROJECT_DESC="Agent Builder Application - AI agent creation, testing, and deployment platform"

# Check if project exists
proj_list=$(gh project list --owner ${{ github.repository_owner }} --format json 2>&1)
Expand Down
22 changes: 15 additions & 7 deletions convex/agentBuilderWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,22 @@ export const executeWorkflowStage = action( {
userPrompt: fullPrompt,
} );

// Meter: token-based billing for this workflow stage
// Meter: token-based billing for this workflow stage (non-fatal)
if ( result.inputTokens > 0 || result.outputTokens > 0 ) {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId as any,
modelId: WORKFLOW_MODEL_ID,
inputTokens: result.inputTokens,
outputTokens: result.outputTokens,
} );
try {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId,
modelId: WORKFLOW_MODEL_ID,
inputTokens: result.inputTokens,
outputTokens: result.outputTokens,
} );
} catch ( billingErr ) {
console.error( "agentBuilderWorkflow: billing failed (non-fatal)", {
userId: gateResult.userId, modelId: WORKFLOW_MODEL_ID,
inputTokens: result.inputTokens, outputTokens: result.outputTokens,
error: billingErr instanceof Error ? billingErr.message : billingErr,
} );
}
}

return {
Expand Down
23 changes: 16 additions & 7 deletions convex/automatedAgentBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,23 @@ export const processResponse = action( {
const systemPrompt = buildSystemPrompt( session.agentRequirements );
const response = await analyzeAndAskNext( systemPrompt, updatedHistory );

// Meter token usage for billing
// Meter token usage for billing (non-fatal: don't kill agent generation)
if ( response.tokenUsage && gateResult.allowed ) {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId as any,
modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
} );
try {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId,
modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
} );
} catch ( billingErr ) {
console.error( "automatedAgentBuilder: billing failed (non-fatal)", {
userId: gateResult.userId, modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
error: billingErr instanceof Error ? billingErr.message : billingErr,
} );
}
}

// Parse response to extract:
Expand Down
23 changes: 13 additions & 10 deletions convex/awsDeployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { action, internalAction, mutation, query, internalMutation, internalQuery } from "./_generated/server";
import { v } from "convex/values";
import { internal, api } from "./_generated/api";
import type { Id } from "./_generated/dataModel";

// Stripe mutations live in stripeMutations.ts. Cast bridges codegen gap.

Expand Down Expand Up @@ -703,7 +704,7 @@ async function deployToAgentCore( artifacts: any, config: any ) {
export const createDeploymentInternal = internalMutation( {
args: {
agentId: v.id( "agents" ),
userId: v.union( v.id( "users" ), v.string() ),
userId: v.id( "users" ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the file and check its existence
find . -name "awsDeployment.ts" -type f

Repository: MikePfunk28/agent_builder_application

Length of output: 106


🏁 Script executed:

# Read the area around line 707 to see createDeploymentInternal
sed -n '700,730p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 1256


🏁 Script executed:

# Read the area around line 989 to see deployTier2
sed -n '980,1010p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 1377


🏁 Script executed:

# Check line 10 for the import
sed -n '1,15p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 783


🏁 Script executed:

# Search for all calls to createDeploymentInternal
rg -n "createDeploymentInternal" convex/awsDeployment.ts -A 2 -B 2

Repository: MikePfunk28/agent_builder_application

Length of output: 804


🏁 Script executed:

# Check the executeCrossAccountDeploymentInternal and executeWebIdentityDeploymentInternal mentioned in scratchpad
rg -n "executeCrossAccountDeploymentInternal\|executeWebIdentityDeploymentInternal" convex/awsDeployment.ts | head -20

Repository: MikePfunk28/agent_builder_application

Length of output: 63


🏁 Script executed:

# Find where deployTier2 is called
rg -n "deployTier2" convex/awsDeployment.ts -B 2 -A 2

Repository: MikePfunk28/agent_builder_application

Length of output: 588


🏁 Script executed:

# Check the context where deployTier2 is called (around line 88)
sed -n '75,100p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 1411


🏁 Script executed:

# Check the function signature that contains this code (around line 75)
sed -n '40,80p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 1914


🏁 Script executed:

# Check what getAuthUserId returns
rg -n "function getAuthUserId\|const getAuthUserId\|export.*getAuthUserId" convex/awsDeployment.ts -A 5

Repository: MikePfunk28/agent_builder_application

Length of output: 63


🏁 Script executed:

# Search for getAuthUserId definition more broadly
rg -n "getAuthUserId" convex/awsDeployment.ts

Repository: MikePfunk28/agent_builder_application

Length of output: 445


🏁 Script executed:

# Check imports at the top of the file more carefully
sed -n '1,50p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 2237


🏁 Script executed:

# Verify the return type of getAuthUserId by checking how it's used throughout
# and looking for null checks
sed -n '44,90p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 2189


🏁 Script executed:

# Check for all calls to deployTier2
rg -n "deployTier2\(" convex/awsDeployment.ts

Repository: MikePfunk28/agent_builder_application

Length of output: 222


🏁 Script executed:

# Verify how getAuthUserId is actually typed in the Convex auth library
# by checking if there are any .d.ts files or checking the node_modules
find . -path "*/node_modules/@convex-dev/auth*" -name "*.d.ts" 2>/dev/null | head -5

Repository: MikePfunk28/agent_builder_application

Length of output: 63


🏁 Script executed:

# Check how userId is used within deployTier2 - particularly when passed to createDeploymentInternal
sed -n '989,1010p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 1064


🏁 Script executed:

# Also check if there are any TypeScript errors or types in comments
rg -n "@convex-dev/auth" . --type=ts -A 2 -B 2 | head -30

Repository: MikePfunk28/agent_builder_application

Length of output: 1652


🏁 Script executed:

# Check all the internal action/mutation signatures that accept userId
rg -n "userId: (string|any)" convex/awsDeployment.ts | head -20

Repository: MikePfunk28/agent_builder_application

Length of output: 166


🏁 Script executed:

# Check all internal action/mutation function signatures to see which ones accept userId parameter
rg -n "async function" convex/awsDeployment.ts | grep -E "deployTier|executeWebIdentity|executeCrossAccount"

Repository: MikePfunk28/agent_builder_application

Length of output: 257


🏁 Script executed:

# Get the full signatures of these functions
sed -n '950,955p' convex/awsDeployment.ts | cat -n

Repository: MikePfunk28/agent_builder_application

Length of output: 417


🏁 Script executed:

# Check executeWebIdentityDeploymentInternal
rg -n "executeWebIdentityDeploymentInternal" convex/awsDeployment.ts -A 5 | head -20

Repository: MikePfunk28/agent_builder_application

Length of output: 571


🏁 Script executed:

# Check executeCrossAccountDeploymentInternal as mentioned in the review scratchpad
rg -n "executeCrossAccountDeploymentInternal" convex/awsDeployment.ts -A 5 | head -30

Repository: MikePfunk28/agent_builder_application

Length of output: 312


deployTier2 still accepts userId: string, creating a type mismatch with createDeploymentInternal.

createDeploymentInternal requires userId: v.id("users") (line 708), but deployTier2 (line 989) declares userId: string and passes it to this mutation (line 1000). While this works at runtime because userId originates from getAuthUserId() which returns Id<"users">, it defeats the purpose of the tightened typing.

Additionally, executeWebIdentityDeploymentInternal (line 1138) and executeCrossAccountDeploymentInternal (line 1063) also declare userId: v.string(), creating further inconsistency in the internal deployment flow.

🔧 Proposed fixes
-async function deployTier2( ctx: any, args: any, userId: string ): Promise<any> {
+async function deployTier2( ctx: any, args: any, userId: Id<"users"> ): Promise<any> {

Also update the internal actions:

-    userId: v.string(),
+    userId: v.id( "users" ),
🤖 Prompt for AI Agents
In `@convex/awsDeployment.ts` at line 707, The deployTier2 →
createDeploymentInternal type mismatch: change deployTier2's userId parameter
(and any internal actions like executeWebIdentityDeploymentInternal and
executeCrossAccountDeploymentInternal) from plain string/v.string() to the
user-id type expected by createDeploymentInternal (use v.id("users") /
Id<"users"> as used in createDeploymentInternal) so the passed userId is
strongly typed; update the function signatures and any internal action/mutation
validators and callers to accept and propagate v.id("users") instead of string
to keep types consistent end-to-end (search for deployTier2,
createDeploymentInternal, executeWebIdentityDeploymentInternal,
executeCrossAccountDeploymentInternal to locate all sites to change).

tier: v.optional( v.string() ),
deploymentConfig: v.object( {
region: v.string(),
Expand All @@ -714,14 +715,9 @@ export const createDeploymentInternal = internalMutation( {
} ),
},
handler: async ( ctx, args ) => {
// Ensure userId is a proper Id<"users">
const userId = typeof args.userId === 'string' && args.userId.startsWith( 'j' )
? args.userId as any
: args.userId;

return await ctx.db.insert( "deployments", {
agentId: args.agentId,
userId: userId,
userId: args.userId,
tier: args.tier || "freemium",
agentName: args.deploymentConfig.agentName,
description: args.deploymentConfig.description,
Expand Down Expand Up @@ -953,7 +949,7 @@ export const executeDeploymentInternal = internalAction( {
/**
* Tier 1: Deploy to YOUR Fargate (Freemium)
*/
async function deployTier1( ctx: any, args: any, userId: string ): Promise<any> {
async function deployTier1( ctx: any, args: any, userId: Id<"users"> ): Promise<any> {
// Create deployment record
const deploymentId: any = await ctx.runMutation( internal.awsDeployment.createDeploymentInternal, {
agentId: args.agentId,
Expand All @@ -962,8 +958,15 @@ async function deployTier1( ctx: any, args: any, userId: string ): Promise<any>
deploymentConfig: args.deploymentConfig,
} );

// Increment usage counter (centralized in stripeMutations.ts)
await ctx.runMutation( internalStripeMutations.incrementUsageAndReportOverage, { userId } );
// Increment usage counter (non-fatal: don't block deployment)
try {
await ctx.runMutation( internalStripeMutations.incrementUsageAndReportOverage, { userId } );
} catch ( billingErr ) {
console.error( "awsDeployment.deployTier1: billing failed (non-fatal)", {
userId,
error: billingErr instanceof Error ? billingErr.message : billingErr,
} );
}

// Start deployment
await ctx.scheduler.runAfter( 0, internal.awsDeployment.executeDeploymentInternal, {
Expand Down
2 changes: 1 addition & 1 deletion convex/awsDiagramGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ export const storeDiagram = mutation({
// Create new diagram
return await ctx.db.insert("diagrams", {
deploymentId: args.deploymentId,
userId: userId as any,
userId: userId,
format: args.format,
content: args.content,
generatedAt: Date.now(),
Expand Down
22 changes: 15 additions & 7 deletions convex/deploymentRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { mutation, query } from "./_generated/server";
import { action } from "./_generated/server";
import { getAuthUserId } from "@convex-dev/auth/server";
import { api, internal } from "./_generated/api";
import type { Id } from "./_generated/dataModel";

// Stripe mutations live in stripeMutations.ts. Cast bridges codegen gap.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -64,7 +65,7 @@ export const getUserTier = query({
});

// Tier 1: Deploy to AgentCore (Freemium)
async function deployTier1(ctx: any, args: any, userId: any): Promise<any> {
async function deployTier1(ctx: any, args: any, userId: Id<"users">): Promise<any> {
// Check usage limits
const user = await ctx.runQuery(api.deploymentRouter.getUserTier);

Expand Down Expand Up @@ -129,11 +130,18 @@ async function deployTier1(ctx: any, args: any, userId: any): Promise<any> {
throw new Error(result.error || "AgentCore deployment failed");
}

// Increment usage counter (centralized in stripeMutations.ts)
await ctx.runMutation( internalStripeMutations.incrementUsageAndReportOverage, {
userId,
modelId: agent.model,
} );
// Increment usage counter (non-fatal: don't block deployment)
try {
await ctx.runMutation( internalStripeMutations.incrementUsageAndReportOverage, {
userId,
modelId: agent.model,
} );
} catch ( billingErr ) {
console.error( "deploymentRouter.deployTier1: billing failed (non-fatal)", {
userId, modelId: agent.model,
error: billingErr instanceof Error ? billingErr.message : billingErr,
} );
}

return {
success: true,
Expand Down Expand Up @@ -251,7 +259,7 @@ export const getDeploymentHistory = query({

let query = ctx.db
.query("deployments")
.withIndex("by_user", (q) => q.eq("userId", userId as any));
.withIndex("by_user", (q) => q.eq("userId", userId));

if (args.agentId) {
query = ctx.db
Expand Down
4 changes: 2 additions & 2 deletions convex/deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const create = mutation({

const deploymentId = await ctx.db.insert("deployments", {
agentId: args.agentId,
userId: userId as any,
userId: userId,
tier: args.tier,
awsAccountId: args.awsAccountId,
region: args.region,
Expand Down Expand Up @@ -74,7 +74,7 @@ export const list = query({

return await ctx.db
.query("deployments")
.withIndex("by_user", (q) => q.eq("userId", userId as any))
.withIndex("by_user", (q) => q.eq("userId", userId))
.order("desc")
.take(args.limit || 20);
},
Expand Down
41 changes: 33 additions & 8 deletions convex/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,19 +517,44 @@ http.route({
}

case "charge.dispute.created": {
// Stripe Dispute: customer is on the linked charge, not top-level.
// Use the raw event data which includes customer as a string.
// Stripe Dispute: customer may be top-level, on expanded charge, or
// require a charge lookup when charge is a string ID (not expanded).
const disputeData = event.data.object as any;
const disputeCustomer: string | undefined =
typeof disputeData.customer === "string"
? disputeData.customer
: typeof disputeData.charge === "string"
? undefined // charge ID only — need to look up; skip for now
: disputeData.charge?.customer;
let disputeCustomer: string | undefined;

if ( typeof disputeData.customer === "string" ) {
// Customer is directly on the dispute object
disputeCustomer = disputeData.customer;
} else if ( typeof disputeData.charge === "string" ) {
// Charge is an unexpanded string ID — fetch it to get customer
try {
const chargeObj = await stripe.charges.retrieve( disputeData.charge );
disputeCustomer = typeof chargeObj.customer === "string"
? chargeObj.customer
: typeof chargeObj.customer === "object" && chargeObj.customer !== null
? chargeObj.customer.id
: undefined;
} catch ( chargeErr ) {
console.error( "Failed to retrieve charge for dispute", {
chargeId: disputeData.charge,
error: chargeErr instanceof Error ? chargeErr.message : chargeErr,
} );
}
} else if ( disputeData.charge?.customer ) {
// Charge is an expanded object with customer
disputeCustomer = typeof disputeData.charge.customer === "string"
? disputeData.charge.customer
: disputeData.charge.customer?.id;
}

if ( disputeCustomer ) {
await ctx.runMutation(internal.stripeMutations.restrictAccountForDispute, {
stripeCustomerId: disputeCustomer,
});
} else {
console.error( "charge.dispute.created: could not resolve customer", {
disputeId: disputeData.id,
} );
}
break;
}
Expand Down
23 changes: 16 additions & 7 deletions convex/interleavedReasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,23 @@ export const sendMessage: any = action( {
args.message
);

// Meter token usage for billing
// Meter token usage for billing (non-fatal: don't kill message delivery)
if ( response.tokenUsage && gateResult.allowed ) {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId as any,
modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
} );
try {
await ctx.runMutation( internal.stripeMutations.incrementUsageAndReportOverage, {
userId: gateResult.userId,
modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
} );
} catch ( billingErr ) {
console.error( "interleavedReasoning: billing failed (non-fatal)", {
userId: gateResult.userId, modelId,
inputTokens: response.tokenUsage.inputTokens,
outputTokens: response.tokenUsage.outputTokens,
error: billingErr instanceof Error ? billingErr.message : billingErr,
} );
}
}
}

Expand Down
16 changes: 13 additions & 3 deletions convex/lib/bedrockGate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/

import { getAuthUserId } from "@convex-dev/auth/server";
import type { Id } from "../_generated/dataModel";
import {
isProviderAllowedForTier,
isBedrockModelAllowedForTier,
Expand All @@ -26,7 +27,7 @@ import {

export interface BedrockAccessGranted {
allowed: true;
userId: string;
userId: Id<"users">;
tier: TierName;
}

Expand Down Expand Up @@ -82,7 +83,7 @@ export async function requireBedrockAccess(
const result = await requireBedrockAccessForUser( user, modelId );
if ( result.allowed ) {
// Override userId with the real authenticated user ID
return { ...result, userId: String( userId ) };
return { ...result, userId };
}
return result;
}
Expand Down Expand Up @@ -210,9 +211,18 @@ export async function requireBedrockAccessForUser(
};
}

if ( !userDoc._id ) {
console.error( "bedrockGate: userDoc is missing _id", { tier, isAnonymous: userDoc.isAnonymous } );
return {
allowed: false,
reason: "User record is incomplete (missing ID). Please sign in again.",
upgradeMessage: "Sign in to continue.",
};
}

return {
allowed: true,
userId: String( userDoc._id || "internal" ),
userId: userDoc._id as Id<"users">,
tier,
};
}
Loading