0) { @ob_end_clean(); } ob_implicit_flush(true); header('Content-Type: application/x-ndjson; charset=utf-8'); header('Cache-Control: no-store'); header('X-Accel-Buffering: no'); $language = 'en'; $chargeTool = 'deep-research'; $startTime = microtime(true); $emit = function (string $event, array $payload = []) use ($startTime): void { $payload['event'] = $event; $payload['t_ms'] = (int)round((microtime(true) - $startTime) * 1000); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; @flush(); }; try { $isMultipart = stripos((string)($_SERVER['CONTENT_TYPE'] ?? ''), 'multipart/form-data') !== false; if ($isMultipart) { $payloadRaw = (string)($_POST['payload'] ?? ''); if ($payloadRaw === '') { throw new DbnToolsHttpException('Multipart request missing payload.', 422, 'missing_payload'); } $input = json_decode($payloadRaw, true); if (!is_array($input)) { throw new DbnToolsHttpException('Invalid payload JSON.', 422, 'invalid_payload_json'); } } else { $raw = file_get_contents('php://input'); if ($raw === false || strlen($raw) > 120000) { throw new DbnToolsHttpException('Request body unreadable or too large.', 413, 'body_too_large'); } $input = json_decode((string)$raw, true); if (!is_array($input)) { throw new DbnToolsHttpException('Request body must be valid JSON.', 400, 'invalid_json'); } } $language = dbnToolsNormalizeLanguage($input['language'] ?? 'en'); $seedQuery = trim((string)($input['query'] ?? '')); $pastedText = dbnToolsInjectDocContent($input, trim((string)($input['paste_text'] ?? ''))); $sliceInput = $input['slices'] ?? []; $controls = is_array($input['controls'] ?? null) ? $input['controls'] : []; $advocateRole = trim((string)($input['advocate_role'] ?? '')); if (mb_strlen($advocateRole, 'UTF-8') > 200) { throw new DbnToolsHttpException('advocate_role is too long.', 422, 'advocate_role_too_long'); } $chargeTool = $advocateRole !== '' ? 'advocate' : 'deep-research'; $run = dbnToolsResolveToolRun($chargeTool, $input); $ftUid = $run['ftUid']; $engine = $run['engine']; $priorContext = is_array($input['prior_context'] ?? null) ? $input['prior_context'] : null; $branchNotes = mb_substr(trim((string)($input['branch_notes'] ?? '')), 0, 1000, 'UTF-8'); $subQsOverride = is_array($input['sub_questions_override'] ?? null) ? $input['sub_questions_override'] : []; $personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '') ? trim($input['profile']) : null; if (mb_strlen($seedQuery, 'UTF-8') > 4000) { throw new DbnToolsHttpException('Query is too long.', 422, 'query_too_long'); } if (mb_strlen($pastedText, 'UTF-8') > 64000) { throw new DbnToolsHttpException('Pasted text is too long.', 422, 'paste_too_long'); } $emit('progress', ['detail' => 'Reading upload(s)…']); $uploadedFiles = []; if (!empty($_FILES['files']) && is_array($_FILES['files']['tmp_name'] ?? null)) { $count = count($_FILES['files']['tmp_name']); if ($count > 5) { throw new DbnToolsHttpException('At most 5 files can be uploaded per request.', 413, 'too_many_files'); } for ($i = 0; $i < $count; $i++) { $file = [ 'name' => $_FILES['files']['name'][$i] ?? '', 'type' => $_FILES['files']['type'][$i] ?? '', 'tmp_name' => $_FILES['files']['tmp_name'][$i] ?? '', 'error' => $_FILES['files']['error'][$i] ?? UPLOAD_ERR_NO_FILE, 'size' => $_FILES['files']['size'][$i] ?? 0, ]; $extracted = dbnToolsExtractUploadedFile($file); $uploadedFiles[] = [ 'filename' => $extracted['filename'], 'text' => $extracted['text'], 'chars' => $extracted['chars'], 'truncated' => $extracted['truncated'], ]; $emit('progress', [ 'detail' => sprintf('Extracted %s (%d chars%s)', $extracted['filename'], $extracted['chars'], !empty($extracted['truncated']) ? ', truncated' : '' ), ]); } } $emit('start', [ 'engine' => $engine, 'language' => $language, 'upload_count' => count($uploadedFiles), ]); // Optional: append user's case-context chunks to pasted_text so the agent sees them. $useMyCase = !empty($input['use_my_case']); if ($useMyCase) { $retrievalQuery = $seedQuery !== '' ? $seedQuery : mb_substr($pastedText, 0, 2000, 'UTF-8'); $caseBlock = dbnToolsCaseContext(true, $retrievalQuery, 5); if ($caseBlock !== '') { $pastedText = ($pastedText === '') ? $caseBlock : ($pastedText . "\n\n" . $caseBlock); } } $result = (new DbnDeepResearchAgent())->run( $seedQuery, $pastedText, $uploadedFiles, is_array($sliceInput) ? $sliceInput : [], $engine, $language, $controls, $emit, $advocateRole, $priorContext, $branchNotes, $subQsOverride, $personaSlug ); $result['ok'] = true; $result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000); dbnToolsLogMetadata([ 'tool' => $advocateRole !== '' ? 'advocate' : 'deep_research', 'language' => $language, 'ok' => true, 'latency_ms' => $result['latency_ms'], 'chunk_count' => (int)($result['trace_metadata']['chunk_count'] ?? 0), 'source_count' => (int)($result['trace_metadata']['source_count'] ?? 0), 'deployment' => $result['trace_metadata']['deployment'] ?? null, 'advocate_role' => $advocateRole !== '' ? $advocateRole : null, ]); $ftRemaining = $run['credits'] === null ? dbnToolsFreeTierDeduct($ftUid, $chargeTool) : dbnToolsFreeTierDeductAmount($ftUid, $chargeTool, $run['credits'], $run['metadata']); if ($ftRemaining >= 0) { $result['balance'] = $ftRemaining; } $emit('final', ['result' => $result]); } catch (DbnToolsHttpException $e) { $latency = (int)round((microtime(true) - $startTime) * 1000); dbnToolsLogMetadata([ 'tool' => 'deep_research', 'language' => $language, 'ok' => false, 'latency_ms' => $latency, 'error_code' => $e->errorCode, ]); $emit('error', ['code' => $e->errorCode, 'message' => $e->getMessage(), 'status' => $e->status]); } catch (Throwable $e) { error_log('DBN deep research fatal: ' . $e->getMessage()); $latency = (int)round((microtime(true) - $startTime) * 1000); dbnToolsLogMetadata([ 'tool' => 'deep_research', 'language' => $language, 'ok' => false, 'latency_ms' => $latency, 'error_code' => 'internal_error', ]); $emit('error', ['code' => 'internal_error', 'message' => 'The agent could not complete this request.']); }