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 packages/core/src/services/message-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface NormalizedAttachment {
readonly mimeType?: string;
readonly filename?: string;
readonly size?: number;
readonly path?: string;
}

export interface NormalizedToolCall {
Expand Down
4 changes: 2 additions & 2 deletions packages/gateway/src/config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ export const HSTS_MAX_AGE_PRELOAD = 63_072_000;
/** HSTS max-age without preload (1 year, seconds) */
export const HSTS_MAX_AGE = 31_536_000;

/** Default HTTP request body size limit (bytes) — 1 MB */
export const DEFAULT_BODY_LIMIT_BYTES = 1_048_576;
/** Default HTTP request body size limit (bytes) — 10 MB */
export const DEFAULT_BODY_LIMIT_BYTES = 10 * 1024 * 1024;

/** HTTP 413 Payload Too Large status code */
export const HTTP_PAYLOAD_TOO_LARGE = 413;
Expand Down
12 changes: 11 additions & 1 deletion packages/gateway/src/db/repositories/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,17 @@ export class ChatRepository extends BaseRepository {
input.isError || false,
input.inputTokens || null,
input.outputTokens || null,
input.attachments?.length ? JSON.stringify(input.attachments) : null,
input.attachments?.length
? JSON.stringify(
input.attachments.map((a) => ({
type: a.type,
mimeType: a.mimeType,
filename: a.filename,
size: a.size,
path: a.path,
}))
)
: null,
now,
]
);
Expand Down
17 changes: 9 additions & 8 deletions packages/gateway/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ export function createAuthMiddleware(config: AuthConfig) {
return next();
}

const authHeader = c.req.header('Authorization');
const authHeader = c.req.header('Authorization');
const queryToken = c.req.query('token');

if (config.type === 'api-key') {
// Check for API key in header
const apiKey = authHeader?.startsWith('Bearer ')
? authHeader.slice(7)
: c.req.header('X-API-Key');
if (config.type === 'api-key') {
// Check for API key in header or query
const apiKey = authHeader?.startsWith('Bearer ')
? authHeader.slice(7)
: c.req.header('X-API-Key') || queryToken;

if (!apiKey) {
return apiError(c, { code: ERROR_CODES.UNAUTHORIZED, message: 'API key required' }, 401);
Expand All @@ -81,11 +82,11 @@ export function createAuthMiddleware(config: AuthConfig) {
);
}

if (!authHeader?.startsWith('Bearer ')) {
if (!authHeader?.startsWith('Bearer ') && !queryToken) {
return apiError(c, { code: ERROR_CODES.UNAUTHORIZED, message: 'JWT token required' }, 401);
}

const token = authHeader.slice(7);
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : queryToken!;

try {
const payload = await validateJWT(token, config.jwtSecret);
Expand Down
4 changes: 2 additions & 2 deletions packages/gateway/src/middleware/ui-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const uiSessionMiddleware = createMiddleware(async (c, next) => {
return next();
}

// 2. Check for session token
const token = c.req.header('X-Session-Token');
// 2. Check for session token (header or query param for images)
const token = c.req.header('X-Session-Token') || c.req.query('token');
if (token && validateSession(token)) {
c.set('sessionAuthenticated', true);
c.set('userId', 'default');
Expand Down
4 changes: 3 additions & 1 deletion packages/gateway/src/middleware/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ export const chatMessageSchema = z.object({
.array(
z.object({
type: z.enum(['image', 'file']),
data: z.string().max(20_000_000),
data: z.string().max(20_000_000).optional(),
path: z.string().max(2000).optional(),
mimeType: z.string().max(100),
filename: z.string().max(255).optional(),
size: z.number().int().min(0).optional(),
})
)
.max(5)
Expand Down
26 changes: 23 additions & 3 deletions packages/gateway/src/routes/chat-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ import type { ChannelIncomingMessage } from '@ownpilot/core';

const log = getLog('ChatHistory');

function normalizeTrace(trace: any): any {
if (!trace) return trace;
return {
...trace,
modelCalls: trace.modelCalls || [],
autonomyChecks: trace.autonomyChecks || [],
dbOperations: trace.dbOperations || { reads: 0, writes: 0 },
memoryOps: trace.memoryOps || { adds: 0, recalls: 0 },
triggersFired: trace.triggersFired || [],
errors: trace.errors || [],
events: trace.events || [],
toolCalls: trace.toolCalls || [],
};
}

export const chatHistoryRoutes = new Hono();

// =====================================================
Expand Down Expand Up @@ -253,8 +268,9 @@ chatHistoryRoutes.get('/history/:id', async (c) => {
content: msg.role === 'assistant' ? stripInternalTags(msg.content) : msg.content,
provider: msg.provider,
model: msg.model,
attachments: msg.attachments,
toolCalls: msg.toolCalls,
trace: msg.trace,
trace: normalizeTrace(msg.trace),
isError: msg.isError,
createdAt: msg.createdAt.toISOString(),
})),
Expand Down Expand Up @@ -315,11 +331,12 @@ chatHistoryRoutes.get('/history/:id/unified', async (c) => {
provider: msg.provider,
model: msg.model,
toolCalls: msg.toolCalls,
trace: msg.trace,
trace: normalizeTrace(msg.trace),
isError: msg.isError,
createdAt: msg.createdAt.toISOString(),
source: 'web' as const,
direction: msg.role === 'user' ? 'inbound' : 'outbound',
attachments: msg.attachments ?? undefined,
})),
});
}
Expand Down Expand Up @@ -356,6 +373,7 @@ chatHistoryRoutes.get('/history/:id/unified', async (c) => {
direction: 'inbound' | 'outbound';
senderName?: string;
senderId?: string;
attachments?: any[];
};

const unified: UnifiedMessage[] = [];
Expand All @@ -371,6 +389,7 @@ chatHistoryRoutes.get('/history/:id/unified', async (c) => {
direction: cm.direction,
senderName: cm.senderName,
senderId: cm.senderId,
attachments: cm.attachments ?? undefined,
});
}

Expand Down Expand Up @@ -400,11 +419,12 @@ chatHistoryRoutes.get('/history/:id/unified', async (c) => {
provider: msg.provider,
model: msg.model,
toolCalls: msg.toolCalls,
trace: msg.trace,
trace: normalizeTrace(msg.trace),
isError: msg.isError,
createdAt: msg.createdAt.toISOString(),
source: 'ai',
direction: msg.role === 'user' ? 'inbound' : 'outbound',
attachments: msg.attachments ?? undefined,
});
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/gateway/src/routes/chat-legacy-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ export async function handleLegacySend(params: LegacySendParams): Promise<Respon
historyLength: body.historyLength,
ipAddress: c.req.header('x-forwarded-for') || c.req.header('x-real-ip'),
userAgent: c.req.header('user-agent'),
attachments: body.attachments,
});

return c.json(response);
Expand Down
16 changes: 15 additions & 1 deletion packages/gateway/src/routes/chat-streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { generateApprovalId, createApprovalRequest } from '../services/execution
import type { getAgent } from './agent-service.js';
import { ConversationService, runPostChatProcessing } from '../services/conversation-service.js';
import type { McpToolEvent } from '../mcp/mcp-events.js';
import { hydrateAttachments } from './helpers.js';

/**
* Extract display-friendly tool name and args from a ToolCall.
Expand Down Expand Up @@ -594,7 +595,14 @@ export async function processStreamingViaBus(
provider?: string;
model?: string;
workspaceId?: string;
attachments?: Array<{ type: string; data: string; mimeType: string; filename?: string }>;
attachments?: Array<{
type: 'image' | 'file';
data?: string;
path?: string;
mimeType: string;
filename?: string;
size?: number;
}>;
thinking?: {
type: 'enabled' | 'adaptive';
budgetTokens?: number;
Expand Down Expand Up @@ -640,6 +648,9 @@ export async function processStreamingViaBus(
contextWindowOverride,
});

// Hydrate attachment data from disk for the LLM if only path is provided
hydrateAttachments(userId, body.attachments);

// Normalize into NormalizedMessage
const normalized: NormalizedMessage = {
id: crypto.randomUUID(),
Expand All @@ -650,8 +661,10 @@ export async function processStreamingViaBus(
attachments: body.attachments.map((a) => ({
type: a.type as 'image' | 'file',
data: a.data,
path: a.path,
mimeType: a.mimeType,
filename: a.filename,
size: a.size,
})),
}),
metadata: {
Expand Down Expand Up @@ -722,6 +735,7 @@ export async function processStreamingViaBus(
toolCalls,
finishReason: result.response.metadata.finishReason as string | undefined,
historyLength: body.historyLength,
attachments: body.attachments,
});

// Post-processing middleware skips web UI memory extraction.
Expand Down
Loading