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
153 changes: 153 additions & 0 deletions apps/admin/src/lib/api/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,157 @@ export interface paths {
patch?: never;
trace?: never;
};
"/logs/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Single log entry */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
log: {
id: string;
requestId: string;
createdAt: string;
updatedAt: string;
organizationId: string;
projectId: string;
apiKeyId: string;
duration: number;
requestedModel: string;
requestedProvider: string | null;
usedModel: string;
usedProvider: string;
responseSize: number;
content: string | null;
reasoningContent: string | null;
unifiedFinishReason: string | null;
finishReason: string | null;
promptTokens: string | null;
completionTokens: string | null;
totalTokens: string | null;
reasoningTokens: string | null;
messages?: unknown;
temperature: number | null;
maxTokens: number | null;
topP: number | null;
frequencyPenalty: number | null;
presencePenalty: number | null;
reasoningEffort: string | null;
responseFormat?: unknown;
tools: ({
/** @enum {string} */
type: "function";
function: {
name: string;
description?: string;
parameters?: {
[key: string]: unknown;
};
};
} | {
/** @enum {string} */
type: "web_search";
user_location?: {
city?: string;
region?: string;
country?: string;
timezone?: string;
};
/** @enum {string} */
search_context_size?: "low" | "medium" | "high";
max_uses?: number;
})[] | null;
toolChoice: "none" | "auto" | "required" | {
/** @enum {string} */
type: "function";
function: {
name: string;
};
} | unknown;
toolResults: {
id: string;
/** @enum {string} */
type: "function";
function: {
name: string;
arguments: string;
};
}[] | null;
hasError: boolean | null;
errorDetails: {
statusCode: number;
statusText: string;
responseText: string;
} | null;
cost: number | null;
inputCost: number | null;
outputCost: number | null;
requestCost: number | null;
imageInputTokens: string | null;
imageOutputTokens: string | null;
imageInputCost: number | null;
imageOutputCost: number | null;
estimatedCost: boolean | null;
canceled: boolean | null;
streamed: boolean | null;
cached: boolean | null;
customHeaders?: unknown;
/** @enum {string} */
mode: "api-keys" | "credits" | "hybrid";
/** @enum {string} */
usedMode: "api-keys" | "credits";
source: string | null;
routingMetadata?: {
availableProviders?: string[];
selectedProvider?: string;
selectionReason?: string;
providerScores?: {
providerId: string;
score: number;
uptime?: number;
latency?: number;
price?: number;
}[];
} | null;
};
};
};
};
/** @description Log not found */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/logs": {
parameters: {
query?: never;
Expand Down Expand Up @@ -815,6 +966,7 @@ export interface paths {
requestCount: number;
inputTokens: number;
outputTokens: number;
cachedTokens: number;
totalTokens: number;
cost: number;
inputCost: number;
Expand All @@ -823,6 +975,7 @@ export interface paths {
dataStorageCost: number;
imageInputCost: number;
imageOutputCost: number;
cachedInputCost: number;
errorCount: number;
errorRate: number;
cacheCount: number;
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/routes/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const dailyActivitySchema = z.object({
requestCount: z.number(),
inputTokens: z.number(),
outputTokens: z.number(),
cachedTokens: z.number(),
totalTokens: z.number(),
cost: z.number(),
inputCost: z.number(),
Expand All @@ -35,6 +36,7 @@ const dailyActivitySchema = z.object({
dataStorageCost: z.number(),
imageInputCost: z.number(),
imageOutputCost: z.number(),
cachedInputCost: z.number(),
errorCount: z.number(),
errorRate: z.number(),
cacheCount: z.number(),
Expand Down Expand Up @@ -137,6 +139,10 @@ activity.openapi(getActivity, async (c) => {
sql<number>`COALESCE(SUM(CAST(${tables.log.completionTokens} AS NUMERIC)), 0)`.as(
"outputTokens",
),
cachedTokens:
sql<number>`COALESCE(SUM(CAST(${tables.log.cachedTokens} AS NUMERIC)), 0)`.as(
"cachedTokens",
),
totalTokens:
sql<number>`COALESCE(SUM(CAST(${tables.log.totalTokens} AS NUMERIC)), 0)`.as(
"totalTokens",
Expand All @@ -163,6 +169,10 @@ activity.openapi(getActivity, async (c) => {
sql<number>`COALESCE(SUM(${tables.log.imageOutputCost}), 0)`.as(
"imageOutputCost",
),
cachedInputCost:
sql<number>`COALESCE(SUM(${tables.log.cachedInputCost}), 0)`.as(
"cachedInputCost",
),
errorCount:
sql<number>`SUM(CASE WHEN ${tables.log.hasError} = true THEN 1 ELSE 0 END)`.as(
"errorCount",
Expand Down Expand Up @@ -257,6 +267,7 @@ activity.openapi(getActivity, async (c) => {
const requestCount = Number(day.requestCount);
const inputTokens = Number(day.inputTokens);
const outputTokens = Number(day.outputTokens);
const cachedTokens = Number(day.cachedTokens);
const totalTokens = Number(day.totalTokens);
const cost = Number(day.cost);
const inputCost = Number(day.inputCost);
Expand All @@ -265,6 +276,7 @@ activity.openapi(getActivity, async (c) => {
const dataStorageCost = Number(day.dataStorageCost);
const imageInputCost = Number(day.imageInputCost);
const imageOutputCost = Number(day.imageOutputCost);
const cachedInputCost = Number(day.cachedInputCost);
const errorCount = Number(day.errorCount);
const cacheCount = Number(day.cacheCount);
const discountSavings = Number(day.discountSavings);
Expand All @@ -277,6 +289,7 @@ activity.openapi(getActivity, async (c) => {
requestCount,
inputTokens,
outputTokens,
cachedTokens,
totalTokens,
cost,
inputCost,
Expand All @@ -285,6 +298,7 @@ activity.openapi(getActivity, async (c) => {
dataStorageCost,
imageInputCost,
imageOutputCost,
cachedInputCost,
errorCount,
errorRate,
cacheCount,
Expand Down
53 changes: 53 additions & 0 deletions apps/api/src/routes/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,59 @@ const logSchema = z.object({
.optional(),
});

// GET /logs/:id - Fetch a single log by ID
const getById = createRoute({
method: "get",
path: "/{id}",
request: {
params: z.object({
id: z.string().openapi({ description: "Log ID" }),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({ log: logSchema }),
},
},
description: "Single log entry",
},
404: {
description: "Log not found",
},
},
});
Comment thread
smakosh marked this conversation as resolved.

logs.openapi(getById, async (c) => {
const user = c.get("user");

if (!user) {
throw new HTTPException(401, { message: "Unauthorized" });
}

const { id } = c.req.valid("param");

const log = await db.query.log.findFirst({
where: { id },
});
Comment on lines +138 to +140

Copy link
Copy Markdown
Contributor

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:

#!/bin/bash
# Search for findFirst and findMany with their where clauses
rg -n 'findFirst|findMany' --type=ts -A5 -g '!node_modules/**' | head -150

Repository: theopenco/llmgateway

Length of output: 8556


🏁 Script executed:

#!/bin/bash
# Check the exact code in logs.ts around lines 138-140
sed -n '130,150p' apps/api/src/routes/logs.ts

# Search for any findFirst/findMany with bare value where clauses (pattern: where: { [a-zA-Z_]: [^{] )
echo "---"
echo "Searching for any bare value where clauses in findFirst/findMany:"
rg 'findFirst|findMany' --type=ts -A3 | rg -B2 'where:\s*\{\s*[a-zA-Z_]+\s*\}' || echo "No bare value where clauses found"

Repository: theopenco/llmgateway

Length of output: 759


🏁 Script executed:

#!/bin/bash
# Search for the logSchema definition in logs.ts
rg -n 'logSchema' apps/api/src/routes/logs.ts -B5 -A15 | head -50

# Also check the database schema for log table
echo "---"
echo "Checking database schema for log table:"
find . -path ./node_modules -prune -o -name "*.ts" -type f -exec rg -l "export const log" {} \; | grep -i schema

Repository: theopenco/llmgateway

Length of output: 1507


🏁 Script executed:

#!/bin/bash
# Check the database schema for log table
rg -n "export const log" packages/db/src/schema.ts -A100 | head -120

# Also verify if there are any instances where bare value where clause works
echo "---"
echo "Checking if Drizzle ORM version supports bare value where syntax:"
cat packages/db/package.json | rg -A2 'drizzle-orm'

Repository: theopenco/llmgateway

Length of output: 3462


🏁 Script executed:

#!/bin/bash
# Check if the bare value where syntax is valid in Drizzle 1.0.0-beta.1
# Search for any documentation or examples in the codebase about where clause syntax
rg -n 'where:' packages/db/src -B2 -A2 | head -80

# Also check if there's a types file that might show the expected where clause structure
echo "---"
echo "Looking for Drizzle type definitions or examples:"
find . -path ./node_modules -prune -o -name "*.ts" -type f -exec rg -l "type.*where" {} \; | head -5

Repository: theopenco/llmgateway

Length of output: 1274


🏁 Script executed:

#!/bin/bash
# Check if the logs.ts file has any tests or if this endpoint is tested
find . -path ./node_modules -prune -o -name "*.spec.ts" -o -name "*.test.ts" | xargs rg -l "logs" 2>/dev/null | head -5

# Check the GET /logs/:id endpoint more thoroughly
echo "---"
sed -n '100,160p' apps/api/src/routes/logs.ts

# Verify Drizzle 1.0.0-beta.1 where clause syntax by checking their examples
echo "---"
echo "Checking if bare value syntax appears anywhere else in codebase:"
rg 'findFirst|findMany' --type=ts -A2 | rg -B2 'where:\s*{\s*[a-zA-Z_]+\s*:' | head -30

Repository: theopenco/llmgateway

Length of output: 1690


🏁 Script executed:

#!/bin/bash
# Fix the regex and search for where clauses more carefully
echo "Looking for all where clauses in findFirst/findMany calls:"
rg 'findFirst|findMany' --type=ts -A3 | grep -A2 'where:' | head -60

# Check if there are any tests for the logs.ts endpoint
echo "---"
echo "Checking logs.spec.ts for getById test:"
rg -n 'getById|/logs.*id' apps/api/src/routes/logs.ts.spec.ts 2>/dev/null || echo "No logs.spec.ts file found for routes"

# Check if there's a test file for the API routes
ls -la apps/api/src/routes/*.spec.ts 2>/dev/null || echo "No spec files in routes directory"

Repository: theopenco/llmgateway

Length of output: 2806


🏁 Script executed:

#!/bin/bash
# Check the logs.spec.ts file for tests on the getById endpoint
rg -n 'getById|/:id|findFirst' apps/api/src/routes/logs.spec.ts -B3 -A5 | head -80

# Also check the exact line where the issue occurs to see context
echo "---"
echo "Checking the findFirst call with bare value syntax in logs.ts:"
sed -n '135,145p' apps/api/src/routes/logs.ts

Repository: theopenco/llmgateway

Length of output: 316


🏁 Script executed:

#!/bin/bash
# Check the beacon.ts and stripe.ts files to see if they're using a different syntax
echo "Checking beacon.ts where clause:"
rg -n 'where.*type.*self-host' apps/api/src/lib/beacon.ts -B2 -A2

echo "---"
echo "Checking stripe.ts where clauses:"
rg -n 'where.*id:.*organizationId' apps/api/src/stripe.ts -B2 -A2

echo "---"
echo "Checking if these are different query methods (not findFirst/findMany):"
rg -n 'where:' apps/api/src/stripe.ts -B5 | head -40

Repository: theopenco/llmgateway

Length of output: 1413


🏁 Script executed:

#!/bin/bash
# Get the full where clauses from stripe.ts
rg -n 'findFirst' apps/api/src/stripe.ts -A5 | head -100

Repository: theopenco/llmgateway

Length of output: 2032


🏁 Script executed:

#!/bin/bash
# Check authorization.ts to see the pattern mentioned in the review
rg -n 'getActiveUserOrganizationIds' apps/api/src -A15 | head -50

echo "---"
echo "Check if there's a pattern in what files use bare values vs explicit operators:"
echo "Bare values (stripe.ts lines 27, 116, 141):"
sed -n '25,30p;114,119p;139,144p' apps/api/src/stripe.ts

echo "---"
echo "Files using explicit operators:"
rg -n 'findFirst|findMany' apps/api/src/lib/beacon.ts -A3 | head -20

Repository: theopenco/llmgateway

Length of output: 3264


🏁 Script executed:

#!/bin/bash
# Check if there are ANY instances of the shorthand { id } syntax vs { id: id }
echo "Searching for shorthand syntax like { id } or { userId }:"
rg -n 'where:\s*{\s*[a-zA-Z_]+\s*}' apps/api/src --type=ts

echo "---"
echo "Checking if all bare values use the full form { field: value }:"
rg -n 'where:' apps/api/src/stripe.ts -A1 | grep -v 'eq:' | head -30

Repository: theopenco/llmgateway

Length of output: 736


🏁 Script executed:

#!/bin/bash
# Check the context around stripe.ts line 1041
sed -n '1035,1050p' apps/api/src/stripe.ts

Repository: theopenco/llmgateway

Length of output: 454


🏁 Script executed:

#!/bin/bash
# Check if stripe.ts tests pass or if there are runtime errors
echo "Looking for tests of the findMany with bare value syntax in stripe.ts:"
rg -n 'stripe' apps/api/src/routes --type=ts -l

echo "---"
echo "Check stripe.ts to see if it's a working, tested file:"
head -5 apps/api/src/stripe.ts

echo "---"
echo "Summary: Count usage patterns in the codebase"
echo "Pattern 1: { field: { eq: value } } - explicit operator:"
rg 'where:' apps/api/src --type=ts -A2 | grep -c 'eq:'

echo "Pattern 2: { field: value } - bare value/shorthand (not wrapped in eq):"
rg 'where:' apps/api/src --type=ts -A2 | grep -E 'field|organizationId|userId|id' | grep -v 'eq:' | grep -v '{' | wc -l

echo "---"
echo "Checking beacon.ts where clause pattern more carefully:"
sed -n '78,85p' apps/api/src/lib/beacon.ts

Repository: theopenco/llmgateway

Length of output: 961


🏁 Script executed:

#!/bin/bash
# Verify that stripe.ts with bare value syntax is production code and not just tests
echo "Check git log to see if stripe.ts is actively used:"
git log --oneline apps/api/src/stripe.ts 2>/dev/null | head -5 || echo "Git not available"

echo "---"
echo "Check if stripe.ts is imported and used in production routes:"
rg 'from.*stripe' apps/api/src/routes --type=ts

echo "---"
echo "Verify Drizzle documentation comment in the codebase about object syntax:"
rg -n 'object query\|relational query API' packages/db/src -B2 -A2 | head -30

Repository: theopenco/llmgateway

Length of output: 639


🏁 Script executed:

#!/bin/bash
# Final verification: Check if logs.ts imports `eq` but doesn't use it in the findFirst call
echo "logs.ts imports (lines 7-20):"
sed -n '7,20p' apps/api/src/routes/logs.ts

echo "---"
echo "Does logs.ts use eq elsewhere in the file?"
rg -n '\beq\(' apps/api/src/routes/logs.ts | head -10

Repository: theopenco/llmgateway

Length of output: 850


Align the where clause with the predominant codebase pattern for consistency.

While the bare value syntax where: { id } is functional and used in other files (e.g., stripe.ts), most database queries in this codebase use explicit operators like { id: { eq: id } }. For consistency with files like authorization.ts and the rest of logs.ts itself (which imports eq and uses it throughout), consider adopting the explicit operator form:

Suggested change
 const log = await db.query.log.findFirst({
-	where: { id },
+	where: { id: { eq: id } },
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const log = await db.query.log.findFirst({
where: { id },
});
const log = await db.query.log.findFirst({
where: { id: { eq: id } },
});
🤖 Prompt for AI Agents
In `@apps/api/src/routes/logs.ts` around lines 138 - 140, The query using
db.query.log.findFirst currently uses the shorthand where: { id } but the
repository prefers explicit operators; update the where clause in the log
retrieval to use the imported eq operator (e.g., where: { id: { eq: id } }) so
it matches the style used elsewhere in logs.ts and authorization.ts.


if (!log) {
throw new HTTPException(404, { message: "Log not found" });
}

// Verify user has access to this log's organization
const organizationIds = await getActiveUserOrganizationIds(user.id);

if (!organizationIds.includes(log.organizationId)) {
throw new HTTPException(403, {
message: "You don't have access to this log",
});
}

return c.json({ log });
});

const querySchema = z.object({
apiKeyId: z.string().optional().openapi({
description: "Filter logs by API key ID",
Expand Down
Loading
Loading