300]; } } namespace app\common\service { class DifyChatService { public static int $calls = 0; public static function chat($profile, $inputs, $query, $user, $files, $options): array { ++self::$calls; ($options['on_timeout'])('synthetic-task'); return ['ok' => false, 'error_code' => 'UPSTREAM_TIMEOUT']; } } } namespace { require dirname(__DIR__) . '/vendor/autoload.php'; use app\adminapi\logic\tcm\DiagnosisAiLogic; use app\adminapi\service\AssistantSseTiming; $fit = new ReflectionMethod(DiagnosisAiLogic::class, 'fitContextForPrompt'); foreach ([false, true] as $throw) { $context = ['case_text' => str_repeat('合成', 21000), 'compaction_cache_scope' => 'synthetic']; $copy = $context; $notifications = 0; $options = ['on_timeout' => static function () use (&$notifications, $throw): void { ++$notifications; if ($throw) throw new RuntimeException('synthetic observer error'); }]; $ok = $fit->invokeArgs(null, [&$context, 'qwen', new AssistantSseTiming(), $options]); if ($ok || $notifications !== 1 || $context !== $copy || DiagnosisAiLogic::getAssistantErrorCode() !== 'UPSTREAM_TIMEOUT' || \think\facade\Cache::$writes !== 0) throw new RuntimeException('Timeout is contained, one-shot, and never caches partial history'); } echo "Diagnosis history extraction timeout observer integration: OK\n"; }